diff --git a/docs/feed-news-modal/README.md b/docs/feed-news-modal/README.md new file mode 100644 index 000000000..d906d0bfc --- /dev/null +++ b/docs/feed-news-modal/README.md @@ -0,0 +1,65 @@ + + +# Компактная лента и модальное чтение новости + +База DEV после #373: `e2c6d00dded1e026a9ec2850612ea8822f75e9e3`. +Ветка: `fix/dev-feed-news-modal`. Проверка 23–24 сентября 2026, Node 20.20.2. + +## Изменения и границы + +- Для `feedType=project/people` NewsCard делегирует превью локальному FeedNewsPreviewComponent. Полное чтение реализует соседний FeedNewsModalComponent поверх существующего app-modal. Shared modal не изменён. +- Превью: заголовок до двух строк, описание до трёх, thumbnail 120 × 67,5 px. Без изображения media-элемента нет. «Подробнее» появляется по фактическому DOM-обрезанию после layout, изменения ширины или загрузки шрифта. В превью ссылки текста не создают скрытых при clamp tab-stop; в окне сохранены ParseLinksPipe/ParseBreaksPipe. +- Мышь, Enter и Space открывают окно. Like, copy, source, CTA и owner menu остаются самостоятельными действиями. После закрытия через крестик, Escape или backdrop фокус возвращается на trigger. Focus trap включается при attachment portal; уничтожение владельца до отложенного attachment не оставляет overlay. +- Окно получает тот же FeedNews input; события лайка возвращаются через NewsCard → существующий facade → FeedUIInfoService. Копирование вызывает прежний onCopyLink. Открытие не делает detail HTTP и не добавляет просмотры. IntersectionObserver/read flow не изменены. +- Полный текст не обрезается; файлы доступны как download-ссылки. Пять изображений проверены в существующей карусели с локальным `fit=contain`. Исправлен её существующий дефект: `images.length` читал длину функции signal, теперь `images().length` читает число изображений. Default `cover` для прежних поверхностей сохранён. +- Общие 420 px заменены высотой содержимого; CSS Grid растягивает только соседей одного ряда. CTA выровнены по низу ряда. Порядок 1–6, append 7–12 и breakpoint 1000 px сохранены. +- Новые проекты и вакансии не получают modal, сохраняют CTA и маршруты. Legacy detail/profile NewsCard и редактор не переработаны. +- Backend, React, API-контракт, runtime API-слой, category counts, пагинация, фильтры, зависимости, workflows и test harness не изменены. В API-каталоге добавлен только regression spec существующего UI state. + +## Реальные замеры браузера + +Это настоящие Angular-компоненты FeedComponent/NewsCard/Modal в локальном fixture, со стилями office и синтетическими данными. Сеть/fetch заменены fixture-facade; лайк использует настоящий FeedUIInfoService. Live DEV и реальные пользовательские данные не использовались. + +Масштаб 100%; одинаковые fixture и viewport для before/after. Исходная сборка сделана до правок от exact base. Полные DOM-замеры: [geometry.json](geometry.json). + +| Viewport | Колонки | Ширина карточки | Высота рядов после | До | +| ---------- | ------- | --------------- | -------------------------------------- | ---------- | +| 1440 × 900 | 2 | 423,33 px | 266,5 / 314 / 358 px | все 420 px | +| 1280 × 900 | 2 | 423,33 px | 266,5 / 314 / 358 px | — | +| 1024 × 900 | 2 | 368,75 px | 266,5 / 336 / 358 px | — | +| 390 × 844 | 1 | 375 px | 221 / 266,5 / 314 / 290 / 258 / 358 px | все 420 px | + +На desktop разница высот и Y-позиций CTA внутри каждого ряда — 0 px. Следующие ряды имеют свою высоту. Thumbnail во всех viewport — 120 × 67,5 px, `cover`; no-image карточка не резервирует media-slot. Очень длинный текст без пробелов и 80 абзацев также не создаёт горизонтального переполнения; на desktop ряды 327 / 336 / 358 px. + +Окно: `width: min(880px, calc(100vw - 24px))`, `max-height: 90dvh`. При 1440/1280/1024 × 900 максимум 880 × 810 px. При 390 × 844 — 366 × 759,59 px. В desktop окружении браузер использует классическую полосу прокрутки 15 px, поэтому визуальный отступ от полезной области может отличаться от 12 px. Прокручивается содержимое, header не уезжает. На mobile длинная новость прокручена до `scrollTop=7334`, крестик сохранил Y=52,20 px. + +| Изображение | Исходный размер | Видимая область изображения при 1440 × 900 | +| ----------- | --------------- | ------------------------------------------ | +| Портрет | 600 × 900 | 408 × 612 | +| Квадрат | 600 × 600 | 612 × 612 | +| 4:3 | 800 × 600 | 816 × 612 | +| 16:9 | 960 × 540 | 840 × 472,5 | +| Панорама | 1500 × 300 | 840 × 168 | + +У всех `object-fit: contain`, вся рамка fixture видима. Это размеры отрисованного изображения, вычисленные из natural size и contain; CSS-бокс может включать свободное место по бокам. На mobile портрет — 319 × 478,5 px. Обрезки нет. Проверено переключение всех пяти изображений стрелками. + +В браузере дополнительно проверены Space, «Подробнее», thumbnail, Escape/крестик, возврат фокуса и синхронный лайк (234 → 233 на обеих поверхностях). Внутренние действия/owner menu/append/закрытие backdrop также проверены targeted-тестами. Скачивание внешнего файла и доступность целевой detail-страницы на live DEV не проверялись; в fixture проверены download-атрибуты и существующие маршруты. Clipboard click делегирован прежнему обработчику, содержимое системного буфера браузерной приёмкой не подтверждено. + +В browser log нет errors; есть NG0912 IconComponent collision на исходной и итоговой fixture. [Журнал](browser-log.json). + +## Скриншоты + +Пары A (короткие без/с изображением), B (длинная новость + вакансия), C (новый проект + новость с изображением) находятся в рядах 1, 2, 3 общего скриншота. + +- [Лента до](screenshots/feed-before.png) / [после, все три ряда](screenshots/feed-after.png). +- [1280](screenshots/feed-1280.png), [1024](screenshots/feed-1024.png), [экстремально длинный текст](screenshots/feed-extreme.png). +- [Mobile до](screenshots/mobile-before.png) / [после](screenshots/mobile-after.png), [mobile extreme](screenshots/mobile-extreme.png). +- [Портрет](screenshots/modal-portrait.png), [квадрат](screenshots/modal-square.png), [4:3](screenshots/modal-landscape.png), [16:9](screenshots/modal-wide.png), [панорама](screenshots/modal-panorama.png). +- [Полный текст](screenshots/modal-long-text.png), [длинный текст + landscape, конец](screenshots/modal-long-landscape.png). +- [Mobile modal](screenshots/mobile-modal.png), [длинный текст + portrait, начало](screenshots/mobile-modal-long-top.png) / [конец](screenshots/mobile-modal-long-bottom.png). + +## Воспроизведение fixture + +Сохранены тестовые SVG в `fixtures/` и entry point `visual-fixture.ts`. Он использует только локальные синтетические новости. Для повторения скопировать entry point в `tmp/surface-main.ts` (его относительные imports рассчитаны на tmp), создать временный tsconfig от корневого tsconfig с `files: ["surface-main.ts"]`, подключить обычный Angular index и собрать development с CLI overrides `--browser tmp/surface-main.ts --ts-config tmp/surface-tsconfig.json --output-path tmp/preview-after`. Скопировать SVG в `browser/fixtures`, создать произвольный тестовый `fixtures/notes.txt`, обслужить `browser` локальным SPA-сервером. Адрес `/office/feed`; `?extreme` включает длинные тексты, `?odd` оставляет пять карточек. Production bootstrap, маршруты и конфигурация сборки не меняются. + +Результаты команд сохранены отдельно в [checks.md](checks.md). diff --git a/docs/feed-news-modal/browser-log.json b/docs/feed-news-modal/browser-log.json new file mode 100644 index 000000000..f3f682bcb --- /dev/null +++ b/docs/feed-news-modal/browser-log.json @@ -0,0 +1,26 @@ +[ + { + "level": "warn", + "message": "NG0912: Component ID generation collision detected. Components '_IconComponent' and '_IconComponent' with selector '[appIcon]' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID. Find more at https://v20.angular.dev/errors/NG0912", + "timestamp": "2026-09-23T20:55:07.448Z", + "url": "http://127.0.0.1:4341/chunk-SXTMLKHU.js" + }, + { + "level": "warn", + "message": "NG0912: Component ID generation collision detected. Components '_IconComponent' and '_IconComponent' with selector '[appIcon]' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID. Find more at https://v20.angular.dev/errors/NG0912", + "timestamp": "2026-09-23T20:55:39.781Z", + "url": "http://127.0.0.1:4342/chunk-SXTMLKHU.js" + }, + { + "level": "warn", + "message": "NG0912: Component ID generation collision detected. Components '_IconComponent' and '_IconComponent' with selector '[appIcon]' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID. Find more at https://v20.angular.dev/errors/NG0912", + "timestamp": "2026-09-23T21:02:11.810Z", + "url": "http://127.0.0.1:4342/chunk-SXTMLKHU.js" + }, + { + "level": "warn", + "message": "NG0912: Component ID generation collision detected. Components '_IconComponent' and '_IconComponent' with selector '[appIcon]' generated the same component ID. To fix this, you can change the selector of one of those components or add an extra host attribute to force a different ID. Find more at https://v20.angular.dev/errors/NG0912", + "timestamp": "2026-09-23T21:04:09.483Z", + "url": "http://127.0.0.1:4342/chunk-SXTMLKHU.js" + } +] diff --git a/docs/feed-news-modal/checks.md b/docs/feed-news-modal/checks.md new file mode 100644 index 000000000..2df8be031 --- /dev/null +++ b/docs/feed-news-modal/checks.md @@ -0,0 +1,25 @@ + + +# Проверки + +Node 20.20.2, Windows, 24 сентября 2026. База `e2c6d00dded1e026a9ec2850612ea8822f75e9e3`. + +| Команда | Фактический результат | +| -------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------ | +| `npm ci` | exit 0; manifests/lockfile не менялись | +| `npm run test:ci -- --pool=forks projects/social_platform/src/app/ui/widgets/news-card projects/social_platform/src/app/ui/pages/feed projects/social_platform/src/app/api/feed projects/social_platform/src/app/infrastructure/adapters/feed projects/social_platform/src/app/infrastructure/repository/feed` | 14 файлов, 48/48 тестов, exit 0 | +| `npm run test:ci` | exit 1; известный NG0401 `No platform exists!` в setupTestBed, 385 suites не приступили к тестам | +| `npm run test:ci -- --pool=forks` | 385 файлов, 1775/1775 тестов, exit 0, 190,13 с; unhandled errors отсутствуют | +| `npm run lint:ts` | exit 0; 0 ошибок, 6 существующих unused-eslint-disable warnings вне изменённых файлов | +| `npx stylelint <изменённые SCSS>` | exit 0 | +| `npx prettier --check --ignore-path tmp/empty-ignore <изменённые текстовые файлы>` | exit 0; SCSS проверены явно, несмотря на исключение в основном .prettierignore | +| `npm run build:prod` | exit 0; production compile завершён | +| `git diff --check` | exit 0 | + +Никакие ошибки тестов не подавлялись, harness/skip/exclude/dependencies не менялись. Обычный `test:ci` не объявляется GREEN; forks-запуск выполнен отдельно по разрешённому в ТЗ сценарию. В тестах и локальной browser fixture остаётся существующий NG0912 для IconComponent. + +Build выводит предупреждения существующих шаблонов, Sass/CommonJS и budget warnings. У нового FeedNewsPreviewComponent с общим mixin стили около 5,32 kB при warning-пороге 2 kB; error-порог не превышен. Budget-конфигурация не менялась. Собственное предупреждение Sass mixed declarations устранено переносом `cursor` перед mixin, после чего production build повторён. + +Ограничение Windows: существующий `build:sprite` с одинарными кавычками вокруг glob генерирует пустой sprite. Его изменение возвращено из HEAD; scripts/workflows не менялись. Browser acceptance выполнялась на development-сборке с исходным корректным sprite. Результат команды build:prod — компиляционная проверка, локальный dist не предназначен для deploy. Merge/deploy не выполнялись. + +Targeted-тесты покрывают обе news-роли, медиа/пустые состояния, реальное обрезание через размеры DOM и ResizeObserver, открытия мышью/клавиатурой, исключения для actions, owner menu, полный текст/файлы/карусель, возврат фокуса, lifecycle overlay, единый like-state, неизменные counts, шаг пагинации 6 и append 7–12. Геометрия CSS проверена в настоящем браузере, а не в jsdom: [отчёт](README.md). diff --git a/docs/feed-news-modal/fixtures/landscape.svg b/docs/feed-news-modal/fixtures/landscape.svg new file mode 100644 index 000000000..40ac46455 --- /dev/null +++ b/docs/feed-news-modal/fixtures/landscape.svg @@ -0,0 +1 @@ +800 × 600landscape \ No newline at end of file diff --git a/docs/feed-news-modal/fixtures/panorama.svg b/docs/feed-news-modal/fixtures/panorama.svg new file mode 100644 index 000000000..944faa8a2 --- /dev/null +++ b/docs/feed-news-modal/fixtures/panorama.svg @@ -0,0 +1 @@ +1500 × 300panorama \ No newline at end of file diff --git a/docs/feed-news-modal/fixtures/portrait.svg b/docs/feed-news-modal/fixtures/portrait.svg new file mode 100644 index 000000000..cb0cbd5c7 --- /dev/null +++ b/docs/feed-news-modal/fixtures/portrait.svg @@ -0,0 +1 @@ +600 × 900portrait \ No newline at end of file diff --git a/docs/feed-news-modal/fixtures/square.svg b/docs/feed-news-modal/fixtures/square.svg new file mode 100644 index 000000000..b69f07eb0 --- /dev/null +++ b/docs/feed-news-modal/fixtures/square.svg @@ -0,0 +1 @@ +600 × 600square \ No newline at end of file diff --git a/docs/feed-news-modal/fixtures/wide.svg b/docs/feed-news-modal/fixtures/wide.svg new file mode 100644 index 000000000..7aaf80329 --- /dev/null +++ b/docs/feed-news-modal/fixtures/wide.svg @@ -0,0 +1 @@ +960 × 540wide \ No newline at end of file diff --git a/docs/feed-news-modal/geometry.json b/docs/feed-news-modal/geometry.json new file mode 100644 index 000000000..56b677722 --- /dev/null +++ b/docs/feed-news-modal/geometry.json @@ -0,0 +1,1293 @@ +{ + "feed": [ + { + "version": "before", + "width": 1440, + "cards": [ + { + "h": 420, + "w": 423.328125 + }, + { + "h": 420, + "w": 423.328125 + }, + { + "h": 420, + "w": 423.328125 + }, + { + "h": 420, + "w": 423.328125 + }, + { + "h": 420, + "w": 423.328125 + }, + { + "h": 420, + "w": 423.328125 + } + ] + }, + { + "label": "after", + "cards": [ + { + "ctaY": 416.5, + "h": 266.5, + "media": false, + "more": false, + "w": 423.328125, + "x": 365.828125, + "y": 207 + }, + { + "ctaY": 416.5, + "h": 266.5, + "media": true, + "more": false, + "w": 423.328125, + "x": 809.15625, + "y": 207 + }, + { + "ctaY": 750.5, + "h": 314, + "media": false, + "more": true, + "w": 423.328125, + "x": 365.828125, + "y": 493.5 + }, + { + "ctaY": 750.5, + "h": 314, + "media": false, + "more": false, + "w": 423.328125, + "x": 809.15625, + "y": 493.5 + }, + { + "ctaY": 1128.5, + "h": 358, + "media": false, + "more": false, + "w": 423.328125, + "x": 365.828125, + "y": 827.5 + }, + { + "ctaY": 1128.5, + "h": 358, + "media": true, + "more": true, + "w": 423.328125, + "x": 809.15625, + "y": 827.5 + } + ], + "height": 900, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 1440 + }, + { + "label": "after-final-1440", + "cards": [ + { + "ctaY": 416.5, + "h": 266.5, + "media": false, + "more": false, + "w": 423.328125, + "x": 365.828125, + "y": 207 + }, + { + "ctaY": 416.5, + "h": 266.5, + "media": true, + "more": false, + "w": 423.328125, + "x": 809.15625, + "y": 207 + }, + { + "ctaY": 750.5, + "h": 314, + "media": false, + "more": true, + "w": 423.328125, + "x": 365.828125, + "y": 493.5 + }, + { + "ctaY": 750.5, + "h": 314, + "media": false, + "more": false, + "w": 423.328125, + "x": 809.15625, + "y": 493.5 + }, + { + "ctaY": 1128.5, + "h": 358, + "media": false, + "more": false, + "w": 423.328125, + "x": 365.828125, + "y": 827.5 + }, + { + "ctaY": 1128.5, + "h": 358, + "media": true, + "more": true, + "w": 423.328125, + "x": 809.15625, + "y": 827.5 + } + ], + "height": 900, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 1440 + }, + { + "label": "after-1280", + "cards": [ + { + "ctaY": 416.5, + "h": 266.5, + "media": false, + "more": false, + "w": 423.328125, + "x": 285.828125, + "y": 207 + }, + { + "ctaY": 416.5, + "h": 266.5, + "media": true, + "more": false, + "w": 423.328125, + "x": 729.15625, + "y": 207 + }, + { + "ctaY": 750.5, + "h": 314, + "media": false, + "more": true, + "w": 423.328125, + "x": 285.828125, + "y": 493.5 + }, + { + "ctaY": 750.5, + "h": 314, + "media": false, + "more": false, + "w": 423.328125, + "x": 729.15625, + "y": 493.5 + }, + { + "ctaY": 1128.5, + "h": 358, + "media": false, + "more": false, + "w": 423.328125, + "x": 285.828125, + "y": 827.5 + }, + { + "ctaY": 1128.5, + "h": 358, + "media": true, + "more": true, + "w": 423.328125, + "x": 729.15625, + "y": 827.5 + } + ], + "height": 900, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 1280 + }, + { + "label": "after-1024", + "cards": [ + { + "ctaY": 416.5, + "h": 266.5, + "media": false, + "more": false, + "w": 368.75, + "x": 201.5, + "y": 207 + }, + { + "ctaY": 416.5, + "h": 266.5, + "media": true, + "more": false, + "w": 368.75, + "x": 590.25, + "y": 207 + }, + { + "ctaY": 772.5, + "h": 336, + "media": false, + "more": true, + "w": 368.75, + "x": 201.5, + "y": 493.5 + }, + { + "ctaY": 772.5, + "h": 336, + "media": false, + "more": false, + "w": 368.75, + "x": 590.25, + "y": 493.5 + }, + { + "ctaY": 1150.5, + "h": 358, + "media": false, + "more": false, + "w": 368.75, + "x": 201.5, + "y": 849.5 + }, + { + "ctaY": 1150.5, + "h": 358, + "media": true, + "more": true, + "w": 368.75, + "x": 590.25, + "y": 849.5 + } + ], + "height": 900, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 1024 + }, + { + "label": "after-390", + "cards": [ + { + "ctaY": 679, + "h": 221, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 515 + }, + { + "ctaY": 965.5, + "h": 266.5, + "media": true, + "more": false, + "w": 375, + "x": 0, + "y": 756 + }, + { + "ctaY": 1299.5, + "h": 314, + "media": false, + "more": true, + "w": 375, + "x": 0, + "y": 1042.5 + }, + { + "ctaY": 1609.5, + "h": 290, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 1376.5 + }, + { + "ctaY": 1887.5, + "h": 258, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 1686.5 + }, + { + "ctaY": 2265.5, + "h": 358, + "media": true, + "more": true, + "w": 375, + "x": 0, + "y": 1964.5 + } + ], + "height": 844, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 390 + }, + { + "label": "extreme-390", + "cards": [ + { + "ctaY": 785, + "h": 327, + "media": false, + "more": true, + "w": 375, + "x": 0, + "y": 515 + }, + { + "ctaY": 1132, + "h": 327, + "media": true, + "more": true, + "w": 375, + "x": 0, + "y": 862 + }, + { + "ctaY": 1488, + "h": 336, + "media": false, + "more": true, + "w": 375, + "x": 0, + "y": 1209 + }, + { + "ctaY": 1798, + "h": 290, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 1565 + }, + { + "ctaY": 2076, + "h": 258, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 1875 + }, + { + "ctaY": 2454, + "h": 358, + "media": true, + "more": true, + "w": 375, + "x": 0, + "y": 2153 + } + ], + "height": 844, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 390 + }, + { + "label": "extreme-1440", + "cards": [ + { + "ctaY": 477, + "h": 327, + "media": false, + "more": true, + "w": 423.328125, + "x": 365.828125, + "y": 207 + }, + { + "ctaY": 477, + "h": 327, + "media": true, + "more": true, + "w": 423.328125, + "x": 809.15625, + "y": 207 + }, + { + "ctaY": 833, + "h": 336, + "media": false, + "more": true, + "w": 423.328125, + "x": 365.828125, + "y": 554 + }, + { + "ctaY": 833, + "h": 336, + "media": false, + "more": false, + "w": 423.328125, + "x": 809.15625, + "y": 554 + }, + { + "ctaY": 1211, + "h": 358, + "media": false, + "more": false, + "w": 423.328125, + "x": 365.828125, + "y": 910 + }, + { + "ctaY": 1211, + "h": 358, + "media": true, + "more": true, + "w": 423.328125, + "x": 809.15625, + "y": 910 + } + ], + "height": 900, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 1440 + }, + { + "label": "extreme-1024", + "cards": [ + { + "ctaY": 477, + "h": 327, + "media": false, + "more": true, + "w": 368.75, + "x": 201.5, + "y": 207 + }, + { + "ctaY": 477, + "h": 327, + "media": true, + "more": true, + "w": 368.75, + "x": 590.25, + "y": 207 + }, + { + "ctaY": 833, + "h": 336, + "media": false, + "more": true, + "w": 368.75, + "x": 201.5, + "y": 554 + }, + { + "ctaY": 833, + "h": 336, + "media": false, + "more": false, + "w": 368.75, + "x": 590.25, + "y": 554 + }, + { + "ctaY": 1211, + "h": 358, + "media": false, + "more": false, + "w": 368.75, + "x": 201.5, + "y": 910 + }, + { + "ctaY": 1211, + "h": 358, + "media": true, + "more": true, + "w": 368.75, + "x": 590.25, + "y": 910 + } + ], + "height": 900, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 1024 + }, + { + "label": "extreme-1280", + "cards": [ + { + "ctaY": 477, + "h": 327, + "media": false, + "more": true, + "w": 423.328125, + "x": 285.828125, + "y": 207 + }, + { + "ctaY": 477, + "h": 327, + "media": true, + "more": true, + "w": 423.328125, + "x": 729.15625, + "y": 207 + }, + { + "ctaY": 833, + "h": 336, + "media": false, + "more": true, + "w": 423.328125, + "x": 285.828125, + "y": 554 + }, + { + "ctaY": 833, + "h": 336, + "media": false, + "more": false, + "w": 423.328125, + "x": 729.15625, + "y": 554 + }, + { + "ctaY": 1211, + "h": 358, + "media": false, + "more": false, + "w": 423.328125, + "x": 285.828125, + "y": 910 + }, + { + "ctaY": 1211, + "h": 358, + "media": true, + "more": true, + "w": 423.328125, + "x": 729.15625, + "y": 910 + } + ], + "height": 900, + "overflow": false, + "thumbs": [ + { + "fit": "cover", + "h": 67.5, + "w": 120 + }, + { + "fit": "cover", + "h": 67.5, + "w": 120 + } + ], + "width": 1280 + }, + { + "label": "before-390", + "cards": [ + { + "ctaY": 878, + "h": 420, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 515 + }, + { + "ctaY": 1318, + "h": 420, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 955 + }, + { + "ctaY": 1758, + "h": 420, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 1395 + }, + { + "ctaY": 2198, + "h": 420, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 1835 + }, + { + "ctaY": 2638, + "h": 420, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 2275 + }, + { + "ctaY": 3078, + "h": 420, + "media": false, + "more": false, + "w": 375, + "x": 0, + "y": 2715 + } + ], + "height": 844, + "overflow": false, + "thumbs": [], + "width": 390 + } + ], + "modal": [ + { + "label": "portrait", + "body": { + "h": 810, + "w": 880, + "x": 272.5, + "y": 45 + }, + "close": { + "h": 36, + "w": 36, + "x": 1096.5, + "y": 57 + }, + "focus": "Закрыть новость", + "image": { + "box": { + "h": 612, + "w": 825, + "x": 292.5, + "y": 226.390625 + }, + "fit": "contain", + "naturalH": 900, + "naturalW": 600, + "paintedH": 612, + "paintedW": 408.00000000000006 + }, + "scroll": { + "height": 749, + "overflow": false, + "top": 0, + "total": 857 + }, + "viewport": [1440, 900] + }, + { + "label": "square", + "body": { + "h": 810, + "w": 880, + "x": 272.5, + "y": 45 + }, + "close": { + "h": 36, + "w": 36, + "x": 1096.5, + "y": 57 + }, + "focus": "Следующее изображение", + "image": { + "box": { + "h": 612, + "w": 825, + "x": 292.5, + "y": 174.390625 + }, + "fit": "contain", + "naturalH": 600, + "naturalW": 600, + "paintedH": 612, + "paintedW": 612 + }, + "scroll": { + "height": 749, + "overflow": false, + "top": 52, + "total": 857 + }, + "viewport": [1440, 900] + }, + { + "label": "landscape", + "body": { + "h": 810, + "w": 880, + "x": 272.5, + "y": 45 + }, + "close": { + "h": 36, + "w": 36, + "x": 1096.5, + "y": 57 + }, + "focus": "Следующее изображение", + "image": { + "box": { + "h": 612, + "w": 825, + "x": 292.5, + "y": 174.390625 + }, + "fit": "contain", + "naturalH": 600, + "naturalW": 800, + "paintedH": 612, + "paintedW": 816 + }, + "scroll": { + "height": 749, + "overflow": false, + "top": 52, + "total": 857 + }, + "viewport": [1440, 900] + }, + { + "label": "wide", + "body": { + "h": 778.671875, + "w": 880, + "x": 272.5, + "y": 60.6640625 + }, + "close": { + "h": 36, + "w": 36, + "x": 1096.5, + "y": 72.6640625 + }, + "focus": "Следующее изображение", + "image": { + "box": { + "h": 472.5, + "w": 840, + "x": 292.5, + "y": 242.0546875 + }, + "fit": "contain", + "naturalH": 540, + "naturalW": 960, + "paintedH": 472.5, + "paintedW": 840 + }, + "scroll": { + "height": 718, + "overflow": false, + "top": 0, + "total": 718 + }, + "viewport": [1440, 900] + }, + { + "label": "panorama", + "body": { + "h": 474.171875, + "w": 880, + "x": 272.5, + "y": 212.9140625 + }, + "close": { + "h": 36, + "w": 36, + "x": 1096.5, + "y": 224.9140625 + }, + "focus": "Следующее изображение", + "image": { + "box": { + "h": 168, + "w": 840, + "x": 292.5, + "y": 394.3046875 + }, + "fit": "contain", + "naturalH": 300, + "naturalW": 1500, + "paintedH": 168.00000000000003, + "paintedW": 840.0000000000001 + }, + "scroll": { + "height": 413, + "overflow": false, + "top": 0, + "total": 413 + }, + "viewport": [1440, 900] + }, + { + "label": "portrait-1280", + "body": { + "h": 810, + "w": 880, + "x": 192.5, + "y": 45 + }, + "close": { + "h": 36, + "w": 36, + "x": 1016.5, + "y": 57 + }, + "focus": "Закрыть новость", + "image": { + "box": { + "h": 612, + "w": 825, + "x": 212.5, + "y": 226.390625 + }, + "fit": "contain", + "naturalH": 900, + "naturalW": 600, + "paintedH": 612, + "paintedW": 408.00000000000006 + }, + "scroll": { + "height": 749, + "overflow": false, + "top": 0, + "total": 857 + }, + "viewport": [1280, 900] + }, + { + "label": "long-text-1024", + "body": { + "h": 442.90625, + "w": 880, + "x": 64.5, + "y": 228.546875 + }, + "close": { + "h": 36, + "w": 36, + "x": 888.5, + "y": 240.546875 + }, + "focus": "Закрыть новость", + "image": null, + "scroll": { + "height": 382, + "overflow": false, + "top": 0, + "total": 382 + }, + "viewport": [1024, 900] + }, + { + "label": "portrait-390", + "body": { + "h": 759.59375, + "w": 366, + "x": 4.5, + "y": 42.203125 + }, + "close": { + "h": 36, + "w": 36, + "x": 322.5, + "y": 52.203125 + }, + "focus": "Закрыть новость", + "image": { + "box": { + "h": 478.5, + "w": 319, + "x": 20.5, + "y": 215.59375 + }, + "fit": "contain", + "naturalH": 900, + "naturalW": 600, + "paintedH": 478.49999999999994, + "paintedW": 319 + }, + "scroll": { + "height": 703, + "overflow": false, + "top": 0, + "total": 716 + }, + "viewport": [390, 844] + }, + { + "label": "extreme-portrait-390-top", + "body": { + "h": 759.59375, + "w": 366, + "x": 4.5, + "y": 42.203125 + }, + "close": { + "h": 36, + "w": 36, + "x": 322.5, + "y": 52.203125 + }, + "focus": "Закрыть новость", + "image": { + "box": { + "h": 478.5, + "w": 319, + "x": 20.5, + "y": 7537.328125 + }, + "fit": "contain", + "naturalH": 900, + "naturalW": 600, + "paintedH": 478.49999999999994, + "paintedW": 319 + }, + "scroll": { + "height": 703, + "overflow": false, + "top": 0, + "total": 8037 + }, + "viewport": [390, 844] + }, + { + "label": "extreme-portrait-390-bottom", + "body": { + "h": 759.59375, + "w": 366, + "x": 4.5, + "y": 42.203125 + }, + "close": { + "h": 36, + "w": 36, + "x": 322.5, + "y": 52.203125 + }, + "focus": "Содержимое новости", + "image": { + "box": { + "h": 478.5, + "w": 319, + "x": 20.5, + "y": 7433.328125 + }, + "fit": "contain", + "naturalH": 900, + "naturalW": 600, + "paintedH": 478.49999999999994, + "paintedW": 319 + }, + "scroll": { + "height": 703, + "overflow": false, + "top": 104, + "total": 8037 + }, + "viewport": [390, 844] + }, + { + "label": "extreme-portrait-390-settled", + "body": { + "h": 759.59375, + "w": 366, + "x": 4.5, + "y": 42.203125 + }, + "close": { + "h": 36, + "w": 36, + "x": 322.5, + "y": 52.203125 + }, + "focus": "Содержимое новости", + "image": { + "box": { + "h": 478.5, + "w": 319, + "x": 20.5, + "y": 203.328125 + }, + "fit": "contain", + "naturalH": 900, + "naturalW": 600, + "paintedH": 478.49999999999994, + "paintedW": 319 + }, + "scroll": { + "height": 703, + "overflow": false, + "top": 7334, + "total": 8037 + }, + "viewport": [390, 844] + }, + { + "label": "extreme-landscape-top", + "body": { + "h": 810, + "w": 880, + "x": 272.5, + "y": 45 + }, + "close": { + "h": 36, + "w": 36, + "x": 1096.5, + "y": 57 + }, + "focus": "Закрыть новость", + "image": { + "box": { + "h": 464.0625, + "w": 825, + "x": 292.5, + "y": 3853.671875 + }, + "fit": "contain", + "naturalH": 540, + "naturalW": 960, + "paintedH": 464.0625, + "paintedW": 825 + }, + "scroll": { + "height": 749, + "overflow": false, + "top": 0, + "total": 4379 + }, + "viewport": [1440, 900] + }, + { + "label": "extreme-landscape-bottom", + "body": { + "h": 810, + "w": 880, + "x": 272.5, + "y": 45 + }, + "close": { + "h": 36, + "w": 36, + "x": 1096.5, + "y": 57 + }, + "focus": "Содержимое новости", + "image": { + "box": { + "h": 464.0625, + "w": 825, + "x": 292.5, + "y": 223.671875 + }, + "fit": "contain", + "naturalH": 540, + "naturalW": 960, + "paintedH": 464.0625, + "paintedW": 825 + }, + "scroll": { + "height": 749, + "overflow": false, + "top": 3630, + "total": 4379 + }, + "viewport": [1440, 900] + }, + { + "label": "extreme-landscape-1280", + "body": { + "h": 810, + "w": 880, + "x": 192.5, + "y": 45 + }, + "close": { + "h": 36, + "w": 36, + "x": 1016.5, + "y": 57 + }, + "focus": "Содержимое новости", + "image": { + "box": { + "h": 464.0625, + "w": 825, + "x": 212.5, + "y": 223.671875 + }, + "fit": "contain", + "naturalH": 540, + "naturalW": 960, + "paintedH": 464.0625, + "paintedW": 825 + }, + "scroll": { + "height": 749, + "overflow": false, + "top": 3630, + "total": 4379 + }, + "viewport": [1280, 900] + }, + { + "label": "extreme-landscape-1024", + "body": { + "h": 810, + "w": 880, + "x": 64.5, + "y": 45 + }, + "close": { + "h": 36, + "w": 36, + "x": 888.5, + "y": 57 + }, + "focus": "Содержимое новости", + "image": { + "box": { + "h": 464.0625, + "w": 825, + "x": 84.5, + "y": 223.671875 + }, + "fit": "contain", + "naturalH": 540, + "naturalW": 960, + "paintedH": 464.0625, + "paintedW": 825 + }, + "scroll": { + "height": 749, + "overflow": false, + "top": 3630, + "total": 4379 + }, + "viewport": [1024, 900] + } + ] +} diff --git a/docs/feed-news-modal/screenshots/feed-1024.png b/docs/feed-news-modal/screenshots/feed-1024.png new file mode 100644 index 000000000..a5b5e1372 Binary files /dev/null and b/docs/feed-news-modal/screenshots/feed-1024.png differ diff --git a/docs/feed-news-modal/screenshots/feed-1280.png b/docs/feed-news-modal/screenshots/feed-1280.png new file mode 100644 index 000000000..96883567f Binary files /dev/null and b/docs/feed-news-modal/screenshots/feed-1280.png differ diff --git a/docs/feed-news-modal/screenshots/feed-after.png b/docs/feed-news-modal/screenshots/feed-after.png new file mode 100644 index 000000000..4e665d2c7 Binary files /dev/null and b/docs/feed-news-modal/screenshots/feed-after.png differ diff --git a/docs/feed-news-modal/screenshots/feed-before.png b/docs/feed-news-modal/screenshots/feed-before.png new file mode 100644 index 000000000..6a2d29231 Binary files /dev/null and b/docs/feed-news-modal/screenshots/feed-before.png differ diff --git a/docs/feed-news-modal/screenshots/feed-extreme.png b/docs/feed-news-modal/screenshots/feed-extreme.png new file mode 100644 index 000000000..899ffe407 Binary files /dev/null and b/docs/feed-news-modal/screenshots/feed-extreme.png differ diff --git a/docs/feed-news-modal/screenshots/mobile-after.png b/docs/feed-news-modal/screenshots/mobile-after.png new file mode 100644 index 000000000..056db6620 Binary files /dev/null and b/docs/feed-news-modal/screenshots/mobile-after.png differ diff --git a/docs/feed-news-modal/screenshots/mobile-before.png b/docs/feed-news-modal/screenshots/mobile-before.png new file mode 100644 index 000000000..68a06ef55 Binary files /dev/null and b/docs/feed-news-modal/screenshots/mobile-before.png differ diff --git a/docs/feed-news-modal/screenshots/mobile-extreme.png b/docs/feed-news-modal/screenshots/mobile-extreme.png new file mode 100644 index 000000000..dd2839fdb Binary files /dev/null and b/docs/feed-news-modal/screenshots/mobile-extreme.png differ diff --git a/docs/feed-news-modal/screenshots/mobile-modal-long-bottom.png b/docs/feed-news-modal/screenshots/mobile-modal-long-bottom.png new file mode 100644 index 000000000..baa651ce6 Binary files /dev/null and b/docs/feed-news-modal/screenshots/mobile-modal-long-bottom.png differ diff --git a/docs/feed-news-modal/screenshots/mobile-modal-long-top.png b/docs/feed-news-modal/screenshots/mobile-modal-long-top.png new file mode 100644 index 000000000..c23683e6b Binary files /dev/null and b/docs/feed-news-modal/screenshots/mobile-modal-long-top.png differ diff --git a/docs/feed-news-modal/screenshots/mobile-modal.png b/docs/feed-news-modal/screenshots/mobile-modal.png new file mode 100644 index 000000000..dfa79821c Binary files /dev/null and b/docs/feed-news-modal/screenshots/mobile-modal.png differ diff --git a/docs/feed-news-modal/screenshots/modal-landscape.png b/docs/feed-news-modal/screenshots/modal-landscape.png new file mode 100644 index 000000000..e74de193a Binary files /dev/null and b/docs/feed-news-modal/screenshots/modal-landscape.png differ diff --git a/docs/feed-news-modal/screenshots/modal-long-landscape.png b/docs/feed-news-modal/screenshots/modal-long-landscape.png new file mode 100644 index 000000000..dd97d90e8 Binary files /dev/null and b/docs/feed-news-modal/screenshots/modal-long-landscape.png differ diff --git a/docs/feed-news-modal/screenshots/modal-long-text.png b/docs/feed-news-modal/screenshots/modal-long-text.png new file mode 100644 index 000000000..e2022cb7b Binary files /dev/null and b/docs/feed-news-modal/screenshots/modal-long-text.png differ diff --git a/docs/feed-news-modal/screenshots/modal-panorama.png b/docs/feed-news-modal/screenshots/modal-panorama.png new file mode 100644 index 000000000..3981bb21a Binary files /dev/null and b/docs/feed-news-modal/screenshots/modal-panorama.png differ diff --git a/docs/feed-news-modal/screenshots/modal-portrait.png b/docs/feed-news-modal/screenshots/modal-portrait.png new file mode 100644 index 000000000..744842595 Binary files /dev/null and b/docs/feed-news-modal/screenshots/modal-portrait.png differ diff --git a/docs/feed-news-modal/screenshots/modal-square.png b/docs/feed-news-modal/screenshots/modal-square.png new file mode 100644 index 000000000..ed71970de Binary files /dev/null and b/docs/feed-news-modal/screenshots/modal-square.png differ diff --git a/docs/feed-news-modal/screenshots/modal-wide.png b/docs/feed-news-modal/screenshots/modal-wide.png new file mode 100644 index 000000000..fe424486c Binary files /dev/null and b/docs/feed-news-modal/screenshots/modal-wide.png differ diff --git a/docs/feed-news-modal/visual-fixture.ts b/docs/feed-news-modal/visual-fixture.ts new file mode 100644 index 000000000..5d930002d --- /dev/null +++ b/docs/feed-news-modal/visual-fixture.ts @@ -0,0 +1,165 @@ +/** @format */ + +import "@angular/compiler"; +import { Component, provideZonelessChangeDetection } from "@angular/core"; +import { TestBed } from "@angular/core/testing"; +import { BrowserTestingModule, platformBrowserTesting } from "@angular/platform-browser/testing"; +import { provideRouter, Router, RouterOutlet } from "@angular/router"; +import { provideHttpClient } from "@angular/common/http"; +import { provideNoopAnimations } from "@angular/platform-browser/animations"; +import { API_URL } from "@corelib"; +import { FeedComponent } from "../projects/social_platform/src/app/ui/pages/feed/feed.component"; +import { FeedInfoService } from "@api/feed/facades/feed-info.service"; +import { FeedUIInfoService } from "@api/feed/facades/ui/feed-ui-info.service"; +import { IndustryRepositoryPort } from "@domain/industry/ports/industry.repository.port"; +import { FeedNews } from "@domain/news/project-news.model"; + +const params = new URLSearchParams(location.search); +const long = params.has("extreme"); +const avatar = "/assets/images/profile/main.svg"; +const date = "2026-09-23T12:00:00Z"; +const shapes = ["portrait", "square", "landscape", "wide", "panorama"]; +const media = shapes.map(name => ({ + link: "/fixtures/" + name + ".svg", + name: name + ".svg", + mimeType: "image/svg+xml", + size: 1024, +})); +const paragraph = + "Новый этап помогает командам проверять идеи, обсуждать результаты и находить партнёров. Полный текст сохраняет все подробности и переносы строк."; +const project = { + id: 51, + name: "Команда цифровых решений", + imageAddress: avatar, + industry: 1, + leader: 7, + shortDescription: "Помогаем студентам находить команду.", +}; +const news = (id: number, people: boolean, text: string, files: any[] = []) => ({ + typeModel: "news", + publishedAt: date, + content: { + ...FeedNews.default(), + id, + name: people ? "Анна Смирнова" : project.name, + imageAddress: avatar, + text: long + ? "ОченьДлинныйЗаголовокБезПробелов".repeat(8) + "\n" + (paragraph + "\n").repeat(80) + : text, + contentObject: people ? { id: 42, email: "fixture@example.test" } : project, + files, + }, +}); +const longText = + "Запустили новый этап программы.\n" + + paragraph + + "\nПодробнее: https://example.test/news\n" + + paragraph + + "\n" + + paragraph; +const vacancy = { + typeModel: "vacancy", + publishedAt: date, + content: { + id: 4, + project, + role: "Тестировщик (QA)", + description: "Помогите проверить новую платформу.", + requiredSkills: [ + { id: 1, name: "QA" }, + { id: 2, name: "API" }, + ], + }, +}; +const items: any[] = [ + news(1, true, "Короткая новость."), + news(2, true, "Делюсь иллюстрациями.", media), + news(3, false, longText), + vacancy, + { typeModel: "project", publishedAt: date, content: { ...project, id: 5 } }, + news(6, false, longText, [ + media[3], + { link: "/fixtures/notes.txt", name: "Описание проекта.txt", mimeType: "text/plain", size: 50 }, + ]), +]; +if (params.has("odd")) items.pop(); +const cropStyle = ""; +const ui = new FeedUIInfoService(); +ui.applyInitializationFeedNewsEvent({ + count: 6, + next: "", + previous: "", + results: items, + counts: { all: 86, project: 56, vacancy: 3, news: 27, partnerprogram: 0, education: 0 }, +}); +@Component({ + selector: "app-preview", + imports: [RouterOutlet], + styleUrls: ["../projects/social_platform/src/app/ui/pages/office/office.component.scss"], + template: `
+
+
+
+
+
+ +
+
+
+
+
+
+
`, +}) +class Preview { + cropStyle = cropStyle; +} +TestBed.initTestEnvironment(BrowserTestingModule, platformBrowserTesting()); +TestBed.configureTestingModule({ + imports: [Preview], + providers: [ + provideZonelessChangeDetection(), + provideHttpClient(), + provideNoopAnimations(), + provideRouter([ + { path: "office/feed", component: FeedComponent }, + { path: "", redirectTo: "office/feed", pathMatch: "full" }, + ]), + { provide: API_URL, useValue: "/fixture-api" }, + { + provide: IndustryRepositoryPort, + useValue: { getOne: () => ({ id: 1, name: "Entertainment Tech" }) }, + }, + ], +}).overrideComponent(FeedComponent, { + set: { + providers: [ + { provide: FeedUIInfoService, useValue: ui }, + { + provide: FeedInfoService, + useValue: { + initializationFeedNews() {}, + initScroll() {}, + destroy() {}, + onLike(id: number) { + const index = ui + .feedItems() + .findIndex(x => x.typeModel === "news" && x.content.id === id); + ui.applyLikeNews(index); + }, + }, + }, + ], + }, +}); +TestBed.compileComponents().then(async () => { + const fixture = TestBed.createComponent(Preview); + document.body.appendChild(fixture.nativeElement); + fixture.autoDetectChanges(); + await TestBed.inject(Router).navigateByUrl(location.pathname + location.search); +}); diff --git a/projects/social_platform/src/app/api/feed/facades/ui/feed-ui-info.service.spec.ts b/projects/social_platform/src/app/api/feed/facades/ui/feed-ui-info.service.spec.ts new file mode 100644 index 000000000..01c986c60 --- /dev/null +++ b/projects/social_platform/src/app/api/feed/facades/ui/feed-ui-info.service.spec.ts @@ -0,0 +1,25 @@ +/** @format */ +import { FeedNews } from "@domain/news/project-news.model"; +import { FeedItem } from "@domain/feed/feed-item.model"; +import { FeedUIInfoService } from "./feed-ui-info.service"; + +describe("FeedUIInfoService: единый input карточки и окна", () => { + it("лайк обновляет только нужную новость, сохраняя порядок, page size и counts", () => { + const ui = new FeedUIInfoService(); + const results: FeedItem[] = [1, 2, 3, 4, 5, 6].map(id => ({ + typeModel: "news", + content: { ...FeedNews.default(), id, isUserLiked: false, likesCount: 0 }, + })); + const counts = { all: 86, project: 56, vacancy: 3, news: 27, partnerprogram: 0, education: 0 }; + ui.applyInitializationFeedNewsEvent({ count: 86, results, counts }); + ui.applyLikeNews(1); + expect(ui.feedItems().map(item => item.content.id)).toEqual([1, 2, 3, 4, 5, 6]); + expect(ui.feedItems()[0]).toBe(results[0]); + expect(ui.feedItems()[1].content).toMatchObject({ isUserLiked: true, likesCount: 1 }); + expect(ui.categoryCounts()).toBe(counts); + expect(ui.perFetchTake()).toBe(6); + expect(ui.feedPage()).toBe(6); + ui.applyLikeNews(1); + expect(ui.feedItems()[1].content).toMatchObject({ isUserLiked: false, likesCount: 0 }); + }); +}); diff --git a/projects/social_platform/src/app/ui/pages/feed/feed.component.scss b/projects/social_platform/src/app/ui/pages/feed/feed.component.scss index fa8f1ffcf..bb606b5b7 100644 --- a/projects/social_platform/src/app/ui/pages/feed/feed.component.scss +++ b/projects/social_platform/src/app/ui/pages/feed/feed.component.scss @@ -1,3 +1,5 @@ +/** @format */ + @use "styles/responsive"; .page { @@ -10,7 +12,7 @@ display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: 20px; - align-items: start; + align-items: stretch; @media (max-width: 999px) { grid-template-columns: minmax(0, 1fr); @@ -33,7 +35,7 @@ } &__item { - display: block; + display: grid; min-width: 0; } } diff --git a/projects/social_platform/src/app/ui/pages/feed/feed.component.spec.ts b/projects/social_platform/src/app/ui/pages/feed/feed.component.spec.ts index 0fbaa8f6f..db5e28de0 100644 --- a/projects/social_platform/src/app/ui/pages/feed/feed.component.spec.ts +++ b/projects/social_platform/src/app/ui/pages/feed/feed.component.spec.ts @@ -1,5 +1,8 @@ /** @format */ +import { By } from "@angular/platform-browser"; +import { firstValueFrom } from "rxjs"; +import { ModalComponent } from "@ui/primitives/modal/modal.component"; import { TestBed } from "@angular/core/testing"; import { provideRouter } from "@angular/router"; import { HttpClientTestingModule } from "@angular/common/http/testing"; @@ -72,7 +75,18 @@ describe("FeedComponent: порядок и представление карто { provide: FeedUIInfoService, useValue: ui }, { provide: FeedInfoService, - useValue: { initializationFeedNews: vi.fn(), initScroll: vi.fn(), destroy: vi.fn() }, + useValue: { + initializationFeedNews: vi.fn(), + initScroll: vi.fn(), + destroy: vi.fn(), + onLike(id: number) { + ui.applyLikeNews( + ui + .feedItems() + .findIndex(item => item.typeModel === "news" && item.content.id === id), + ); + }, + }, }, ], }, @@ -116,5 +130,24 @@ describe("FeedComponent: порядок и представление карто .map(card => card.dataset["key"]), ).toEqual(["project:4", "project:5", "project:6", "project:7", "project:8", "project:9"]); expect(ui.perFetchTake()).toBe(6); + const article = root.querySelector('[data-key="news:1"] article')!; + article.click(); + fixture.detectChanges(); + const primitive = fixture.debugElement.query(By.directive(ModalComponent)) + .componentInstance as ModalComponent; + await firstValueFrom(primitive.overlayRef!.attachments()); + await fixture.whenStable(); + const dialog = document.querySelector('[role="dialog"]')!; + const previous = initial[1].content as FeedNews; + dialog.querySelector('[aria-label="Нравится"]')!.click(); + await fixture.whenStable(); + expect(article.querySelector('[aria-label="Нравится"]')?.getAttribute("aria-pressed")).toBe( + String(!previous.isUserLiked), + ); + expect(dialog.querySelector('[aria-label="Нравится"]')?.getAttribute("aria-pressed")).toBe( + String(!previous.isUserLiked), + ); + expect((ui.feedItems()[1].content as FeedNews).viewsCount).toBe(previous.viewsCount); + expect(cards()).toHaveLength(12); }); }); diff --git a/projects/social_platform/src/app/ui/widgets/news-card/carousel/carousel.component.html b/projects/social_platform/src/app/ui/widgets/news-card/carousel/carousel.component.html index ceb82d7c5..36f13f525 100644 --- a/projects/social_platform/src/app/ui/widgets/news-card/carousel/carousel.component.html +++ b/projects/social_platform/src/app/ui/widgets/news-card/carousel/carousel.component.html @@ -1,16 +1,26 @@ @if (images().length) { -