@rt-tools/agent-kit 0.3.0 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +194 -30
- package/assets/agents/business-analyst.md +74 -0
- package/assets/agents/project-manager.md +70 -0
- package/assets/agents/qa-engineer.md +72 -0
- package/assets/agents/skill-curator.md +110 -0
- package/assets/agents/spec-critic.md +44 -0
- package/assets/agents/spec-writer.md +50 -0
- package/assets/checks/board.github.mjs +286 -0
- package/assets/checks/check-board.github.mjs +188 -0
- package/assets/checks/check-doc-paths.mjs +163 -0
- package/assets/checks/check-dupes.mjs +277 -0
- package/assets/checks/check-lib-layers.mjs +573 -0
- package/assets/checks/check-reuse.mjs +208 -0
- package/assets/checks/check-schema-drift.mjs +186 -0
- package/assets/checks/check-specs.mjs +1007 -0
- package/assets/checks/check-styles.mjs +109 -0
- package/assets/checks/rt-kit-checks.config.mjs +134 -0
- package/assets/checks/task-new.github.mjs +198 -0
- package/assets/commands/skill-curator.md +70 -0
- package/assets/defaults/gate-map.sh +100 -0
- package/assets/defaults/project.sh +179 -0
- package/assets/hooks/browser-device-id.sh +0 -0
- package/assets/hooks/browser-guard-device-id.sh +2 -1
- package/assets/hooks/browser-guard-no-asking.sh +27 -0
- package/assets/hooks/browser-guard-no-listing.sh +2 -1
- package/assets/hooks/browser-guard-no-other-drivers.sh +2 -1
- package/assets/hooks/browser-guard-require-select.sh +2 -1
- package/assets/hooks/commit-msg.sh +1 -1
- package/assets/hooks/constitution-index.sh +5 -4
- package/assets/hooks/dev-server-guard.sh +8 -6
- package/assets/hooks/docs-guard.sh +223 -37
- package/assets/hooks/git-guard-delivery.sh +86 -29
- package/assets/hooks/git-guard-main.sh +1 -0
- package/assets/hooks/git-guard-push-tests.sh +34 -13
- package/assets/hooks/glossary-load.sh +23 -0
- package/assets/hooks/lint-after-edit.sh +155 -30
- package/assets/hooks/qa-dataid-guard.sh +72 -32
- package/assets/hooks/reuse-first-guard.sh +105 -34
- package/assets/hooks/skill-gate-rearm.sh +1 -0
- package/assets/hooks/skill-gate.sh +75 -15
- package/assets/hooks/skill-loaded.sh +1 -0
- package/assets/hooks/sql-guard.sh +606 -56
- package/assets/hooks/task-context-load.sh +100 -0
- package/assets/hooks/task-flow-guard.sh +107 -0
- package/assets/laws/{access.md → application/access.md} +1 -4
- package/assets/laws/{locales.md → application/locales.md} +1 -3
- package/assets/laws/application/money.md +41 -0
- package/assets/laws/application/ownership.md +32 -0
- package/assets/laws/{search-visibility.md → application/search-visibility.md} +1 -1
- package/assets/laws/code-structure.md +7 -6
- package/assets/laws/delivery.md +53 -3
- package/assets/laws/entity-editing.md +49 -55
- package/assets/laws/entity-models.md +4 -14
- package/assets/laws/frontend-application.md +5 -5
- package/assets/laws/lib-imports.md +14 -1
- package/assets/laws/lists.md +33 -0
- package/assets/laws/navigation.md +40 -0
- package/assets/laws/project-documentation.md +17 -8
- package/assets/laws/reuse-first.md +26 -21
- package/assets/laws/shared-code.md +13 -1
- package/assets/laws/verifiability.md +17 -1
- package/assets/laws/work-conduct.md +48 -0
- package/assets/patterns/admin-lists-screen.md +131 -0
- package/assets/patterns/admin-nav-item.md +71 -0
- package/assets/patterns/angular-patterns-state.md +29 -22
- package/assets/patterns/api-layer-pair.md +40 -30
- package/assets/patterns/browser-verification-measure.md +41 -38
- package/assets/patterns/browser-verification-stand.md +106 -42
- package/assets/patterns/component-structure-new.md +33 -32
- package/assets/patterns/dependencies-upgrade.md +65 -0
- package/assets/patterns/doc-style-sweep.md +65 -28
- package/assets/patterns/doc-style-write.md +36 -33
- package/assets/patterns/entity-aside.md +136 -0
- package/assets/patterns/entity-models-new.md +124 -0
- package/assets/patterns/entity-store.md +91 -0
- package/assets/patterns/git-workflow-commit.azure.md +259 -0
- package/assets/patterns/git-workflow-commit.github.md +333 -0
- package/assets/patterns/git-workflow-commit.gitlab.md +283 -0
- package/assets/patterns/git-workflow-merge.md +42 -25
- package/assets/patterns/git-workflow-migration.md +61 -31
- package/assets/patterns/git-workflow-restart.md +20 -20
- package/assets/patterns/lib-layers-move.md +50 -32
- package/assets/patterns/lib-layers-new.md +41 -29
- package/assets/patterns/ownership-scope-resolve.md +69 -0
- package/assets/patterns/permissions-procedure.md +35 -33
- package/assets/patterns/platform-access-di.md +39 -25
- package/assets/patterns/pricing-quote.md +71 -0
- package/assets/patterns/reuse-first-extend.md +22 -22
- package/assets/patterns/seo-page.md +52 -40
- package/assets/patterns/seo-verify.md +48 -29
- package/assets/patterns/shared-code-new.md +37 -31
- package/assets/patterns/spec-driven-domain.md +44 -37
- package/assets/patterns/spec-driven-rule.md +55 -40
- package/assets/patterns/styling-bem-component.md +43 -32
- package/assets/patterns/styling-bem-layout.md +30 -24
- package/assets/patterns/task-flow-close.md +90 -0
- package/assets/patterns/task-flow-resume.md +94 -0
- package/assets/patterns/task-flow-start.md +117 -0
- package/assets/patterns/testing-e2e.md +53 -51
- package/assets/patterns/testing-unit.md +70 -46
- package/assets/patterns/translations-key.md +32 -19
- package/assets/patterns/ts-procedure.md +24 -25
- package/assets/rules/angular-patterns.md +46 -27
- package/assets/rules/api-layer.md +46 -28
- package/assets/rules/browser-verification.md +66 -48
- package/assets/rules/component-structure.md +43 -27
- package/assets/rules/dependencies.md +66 -0
- package/assets/rules/doc-style.md +81 -39
- package/assets/rules/entity-conventions.md +78 -0
- package/assets/rules/entity-models.md +70 -0
- package/assets/rules/git-workflow.azure.md +116 -0
- package/assets/rules/git-workflow.github.md +123 -0
- package/assets/rules/git-workflow.gitlab.md +113 -0
- package/assets/rules/lib-layers.md +56 -30
- package/assets/rules/lists.md +73 -0
- package/assets/rules/navigation.md +78 -0
- package/assets/rules/ownership-scope.md +63 -0
- package/assets/rules/permissions.md +43 -25
- package/assets/rules/platform-access.md +57 -29
- package/assets/rules/pricing.md +64 -0
- package/assets/rules/reuse-first.md +57 -43
- package/assets/rules/seo.md +51 -30
- package/assets/rules/shared-code.md +51 -26
- package/assets/rules/spec-driven.md +96 -50
- package/assets/rules/styling-bem.md +54 -39
- package/assets/rules/task-flow.md +110 -0
- package/assets/rules/testing.md +78 -47
- package/assets/rules/translations.md +48 -31
- package/assets/rules/typescript-conventions.md +57 -27
- package/assets/skills/agent-kit.md +81 -0
- package/assets/skills/write-a-skill.md +108 -0
- package/assets/templates/gate-map.sh +23 -15
- package/assets/templates/implementation.md +14 -8
- package/assets/templates/pattern.md +1 -1
- package/assets/templates/project.sh +32 -19
- package/assets/templates/rule.md +1 -1
- package/assets/variants.json +20 -0
- package/assets/workflows/feature.js +134 -0
- package/assets/workflows/plan.js +150 -0
- package/bin/agent-kit.d.ts.map +1 -1
- package/bin/agent-kit.js +78 -5
- package/bin/agent-kit.js.map +1 -1
- package/bin/prompt.d.ts +5 -0
- package/bin/prompt.d.ts.map +1 -1
- package/bin/prompt.js +19 -7
- package/bin/prompt.js.map +1 -1
- package/index.d.ts +1 -0
- package/index.d.ts.map +1 -1
- package/index.js +1 -0
- package/index.js.map +1 -1
- package/lib/assets.d.ts +8 -3
- package/lib/assets.d.ts.map +1 -1
- package/lib/assets.js +13 -3
- package/lib/assets.js.map +1 -1
- package/lib/catalog.d.ts +52 -5
- package/lib/catalog.d.ts.map +1 -1
- package/lib/catalog.js +104 -16
- package/lib/catalog.js.map +1 -1
- package/lib/commands.d.ts +22 -1
- package/lib/commands.d.ts.map +1 -1
- package/lib/commands.js +202 -14
- package/lib/commands.js.map +1 -1
- package/lib/companion.d.ts +5 -1
- package/lib/companion.d.ts.map +1 -1
- package/lib/companion.js +29 -2
- package/lib/companion.js.map +1 -1
- package/lib/config.d.ts +26 -9
- package/lib/config.d.ts.map +1 -1
- package/lib/config.js +41 -15
- package/lib/config.js.map +1 -1
- package/lib/freshness.d.ts +14 -0
- package/lib/freshness.d.ts.map +1 -0
- package/lib/freshness.js +116 -0
- package/lib/freshness.js.map +1 -0
- package/lib/hooks-map.d.ts +24 -0
- package/lib/hooks-map.d.ts.map +1 -0
- package/lib/hooks-map.js +72 -0
- package/lib/hooks-map.js.map +1 -0
- package/lib/integrity.d.ts +36 -0
- package/lib/integrity.d.ts.map +1 -0
- package/lib/integrity.js +44 -0
- package/lib/integrity.js.map +1 -0
- package/lib/picker.d.ts +11 -1
- package/lib/picker.d.ts.map +1 -1
- package/lib/picker.js +44 -6
- package/lib/picker.js.map +1 -1
- package/lib/sync.d.ts +26 -0
- package/lib/sync.d.ts.map +1 -1
- package/lib/sync.js +59 -4
- package/lib/sync.js.map +1 -1
- package/lib/variants.d.ts +44 -0
- package/lib/variants.d.ts.map +1 -0
- package/lib/variants.js +82 -0
- package/lib/variants.js.map +1 -0
- package/package.json +1 -1
- package/rt-tools-agent-kit-0.4.0.tgz +0 -0
- package/assets/laws/admin-lists.md +0 -35
- package/assets/laws/admin-navigation.md +0 -38
- package/assets/patterns/git-workflow-commit.md +0 -175
- package/assets/rules/git-workflow.md +0 -106
- package/rt-tools-agent-kit-0.3.0.tgz +0 -0
|
@@ -2,89 +2,91 @@
|
|
|
2
2
|
name: testing-e2e
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: testing
|
|
5
|
-
description: Паттерн правила testing. Брать при правке и прогоне сквозных спек — что
|
|
5
|
+
description: Паттерн правила testing. Брать при правке и прогоне сквозных спек в apps/site-e2e и apps/admin-e2e — что закрывается сквозной спекой, готовые команды прогона, стенд из прод-сборки под настоящим nginx, выключатели спек. Не брать для юнитов — это паттерн testing-unit.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Сквозные спеки
|
|
9
9
|
|
|
10
10
|
Паттерн правила `testing`. Что при этом должно быть верно — закон
|
|
11
|
-
`
|
|
11
|
+
`docs/constitution/verifiability.md`.
|
|
12
12
|
|
|
13
13
|
## Когда брать
|
|
14
14
|
|
|
15
|
-
- Пишется или правится
|
|
15
|
+
- Пишется или правится спека в `apps/site-e2e/src` или `apps/admin-e2e/src`.
|
|
16
16
|
- Надо воспроизвести дефект, который виден только на прод-конфигурации.
|
|
17
|
-
- Готовится стенд под прогон против
|
|
17
|
+
- Готовится стенд под прогон против nginx.
|
|
18
18
|
|
|
19
19
|
## Сквозной спекой закрывается накопленное состояние
|
|
20
20
|
|
|
21
|
-
Случай для неё — тот, где состояние копится нажатиями: открытая панель, активный маршрут,
|
|
22
|
-
прошлого экрана. Всё, что проверяется одним заходом по адресу, дешевле закрыть юнитом на
|
|
23
|
-
функции или замером экрана.
|
|
21
|
+
Случай для неё — тот, где состояние копится нажатиями: открытая панель, активный маршрут,
|
|
22
|
+
гард прошлого экрана. Всё, что проверяется одним заходом по адресу, дешевле закрыть юнитом на
|
|
23
|
+
чистой функции или замером экрана.
|
|
24
24
|
|
|
25
25
|
Отсюда и способ: нажатия по тем же элементам, что нажимает владелец, несколько подряд, без
|
|
26
26
|
перезагрузки между ними. Заход по прямому адресу поднимает приложение заново, накопленного
|
|
27
|
-
состояния у него нет, и проход читается как «дефект не
|
|
27
|
+
состояния у него нет, и проход читается как «дефект не подтверждается» — так на прод уехала
|
|
28
|
+
панель, застревавшая в адресе при уходе в соседний раздел.
|
|
28
29
|
|
|
29
|
-
Элементы находятся по
|
|
30
|
+
Элементы находятся по `qa-dataid`: классы меняются вместе с вёрсткой, а поиск по роли и тексту
|
|
30
31
|
ломается на переводах.
|
|
31
32
|
|
|
32
33
|
## Прогон
|
|
33
34
|
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
падения на многих рабочих и ноль на одном.
|
|
35
|
+
```bash
|
|
36
|
+
npx nx e2e site-e2e -- --project=chromium
|
|
37
|
+
npx nx e2e admin-e2e -- --project=chromium
|
|
38
|
+
BASE_URL=http://localhost:{{dockerSitePort}} npx nx e2e site-e2e -- --project=chromium # против внешнего стенда
|
|
39
|
+
```
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
По умолчанию конфиг идёт на {{sitePort}} и подхватывает уже поднятый сервер. С `BASE_URL` свой сервер
|
|
42
|
+
не стартует вовсе.
|
|
42
43
|
|
|
43
|
-
|
|
44
|
-
|
|
44
|
+
Полный набор админки гоняется **одним воркером** (`--workers=1`): тесты с настоящей сессией
|
|
45
|
+
правят одни и те же объекты живой базы и в параллельном прогоне мешают друг другу. Одни и те
|
|
46
|
+
же файлы дали три падения на восьми воркерах и ноль на одном.
|
|
45
47
|
|
|
46
|
-
|
|
47
|
-
<сборка всех приложений>
|
|
48
|
-
<адрес хранилища> <порт> node <собранный сервер> &
|
|
49
|
-
docker run -d --name <стенд> \
|
|
50
|
-
--add-host <имя апстрима>:host-gateway \
|
|
51
|
-
-p <внешний порт>:80 \
|
|
52
|
-
-v "$PWD/<конфиг прокси>:/etc/nginx/conf.d/default.conf:ro" \
|
|
53
|
-
-v "$PWD/<каталог сборки>:/usr/share/nginx/html:ro" \
|
|
54
|
-
<образ прокси>
|
|
55
|
-
```
|
|
48
|
+
## Стенд из прод-сборки под настоящим nginx
|
|
56
49
|
|
|
57
|
-
|
|
58
|
-
|
|
50
|
+
Как его поднять — паттерн `browser-verification-stand`; там же сказано, почему главный конфиг
|
|
51
|
+
монтируется отдельной строкой и почему каталогом, а не файлом. Здесь важно одно: против такого
|
|
52
|
+
стенда прогон идёт с `BASE_URL`, и вместе с ним включаются проверки перенаправлений локалей.
|
|
59
53
|
|
|
60
54
|
## Выключатели
|
|
61
55
|
|
|
62
|
-
```
|
|
63
|
-
const
|
|
64
|
-
|
|
56
|
+
```typescript
|
|
57
|
+
const ALLOW_RENAME: boolean = process.env['E2E_ALLOW_SLUG_RENAME'] === '1';
|
|
58
|
+
test.skip(!ALLOW_RENAME, 'меняет живой адрес объекта: включается E2E_ALLOW_SLUG_RENAME=1');
|
|
65
59
|
```
|
|
66
60
|
|
|
67
61
|
- Спека, необратимо меняющая данные стенда, по умолчанию пропускается и включается своей
|
|
68
62
|
переменной.
|
|
69
|
-
-
|
|
70
|
-
страниц их не проходит, и
|
|
71
|
-
|
|
72
|
-
|
|
63
|
+
- `BEHIND_NGINX` в `home.smoke.spec.ts` — это `!!process.env['BASE_URL']`: вместе с ним
|
|
64
|
+
просыпаются проверки перенаправлений локалей. Голый сервер отдачи страниц их не проходит, и
|
|
65
|
+
два падения выглядят регрессией.
|
|
66
|
+
- `HAS_ADMIN_SESSION` в `sign-in.ts` — пара `E2E_ADMIN_EMAIL` и `E2E_ADMIN_PASSWORD`.
|
|
67
|
+
- `playwright/no-skipped-test` выключен в `eslint.config.mjs`: выключатель теста здесь —
|
|
68
|
+
приём, а не забытый `test.skip`.
|
|
73
69
|
|
|
74
70
|
## Частые промахи
|
|
75
71
|
|
|
76
|
-
- **Порт
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
72
|
+
- **Порт {{ssrPort}} занимать осторожно:** стенд разработчика на {{sitePort}} ходит по тому же имени
|
|
73
|
+
`ssr:{{ssrPort}}` через `host-gateway`, и пока на нём висит чужой процесс, стенд отдаёт чужую
|
|
74
|
+
сборку.
|
|
75
|
+
- Браузер стоит один — chromium; узкий экран — `--project=mobile-chrome`. Ошибка «Executable
|
|
76
|
+
doesn't exist» разобрана в правиле `testing`: она же приходит после смены версии Playwright.
|
|
77
|
+
- Спеки админки без сессии пропускаются молча — прогон выглядит успешным, а проверено меньше
|
|
78
|
+
половины.
|
|
79
|
+
- Конфиг nginx монтируется каталогом, а не одиночным файлом: редактор пересоздаёт файл, и
|
|
83
80
|
контейнеру остаётся обрезанная копия.
|
|
84
|
-
- **Подменяется не только то, что запрашивает экран, но и то, что запрашивает шапка.**
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
выглядит дефектом экрана.
|
|
88
|
-
-
|
|
89
|
-
|
|
90
|
-
|
|
81
|
+
- **Подменяется не только то, что запрашивает экран, но и то, что запрашивает шапка.** Счётчик
|
|
82
|
+
непрочитанного запрашивается на каждом экране под шапкой. Без подмены на этот запрос
|
|
83
|
+
отвечает настоящий сервер, поддельный вход он отбивает, интерцептор сбрасывает сессию, и
|
|
84
|
+
пятьдесят девять тестов падают на пропавшей шапке — выглядит это дефектом экрана.
|
|
85
|
+
- **Состояние, которое живёт только во время операции, тестом не проверяется.** Список заливки
|
|
86
|
+
под кнопкой виден, пока файлы летят: на подменённых ответах они долетают раньше, чем тест
|
|
87
|
+
успевает его прочитать, и тест краснеет через раз. Проверяют либо конечное состояние
|
|
88
|
+
(`data-state` строки стал `done`), либо то же промежуточное — но на ответе, который тест сам
|
|
89
|
+
задержал и сам отпускает.
|
|
90
|
+
- **Каталог сборки, удалённый под смонтированным томом, оставляет контейнер с пустым
|
|
91
|
+
корнем:** стенд отвечает 403 на всё, и падают сразу все тесты. Контейнер после
|
|
92
|
+
`rm -rf dist/apps/<приложение>` пересоздаётся.
|
|
@@ -2,92 +2,116 @@
|
|
|
2
2
|
name: testing-unit
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: testing
|
|
5
|
-
description: Паттерн правила testing. Брать при заведении или правке
|
|
5
|
+
description: Паттерн правила testing. Брать при заведении или правке *.spec.ts под Vitest — готовая раскладка describe и it, сборщик фикстур, идентификатор сценария в заголовке, спека процедуры Connect с рукописным двойником базы. Не брать для сквозных спек — это паттерн testing-e2e.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Спека на чистую функцию и на
|
|
8
|
+
# Спека на чистую функцию и на процедуру
|
|
9
9
|
|
|
10
10
|
Паттерн правила `testing`. Что при этом должно быть верно — закон
|
|
11
|
-
`
|
|
11
|
+
`docs/constitution/verifiability.md`.
|
|
12
12
|
|
|
13
13
|
## Когда брать
|
|
14
14
|
|
|
15
|
-
- Заводится или правится
|
|
16
|
-
- Логику надо вынести из компонента или
|
|
17
|
-
- Пишется спека на
|
|
15
|
+
- Заводится или правится `*.spec.ts` рядом с исходником.
|
|
16
|
+
- Логику надо вынести из компонента или сервиса, чтобы её стало чем проверить.
|
|
17
|
+
- Пишется спека на процедуру Connect.
|
|
18
18
|
|
|
19
19
|
## Импорты явные
|
|
20
20
|
|
|
21
|
-
|
|
22
|
-
без конфига, не должен зависеть от настройки, о которой в нём ни слова.
|
|
21
|
+
`globals: true` в конфиге стоит, но список всё равно пишется:
|
|
23
22
|
|
|
24
|
-
|
|
23
|
+
```typescript
|
|
24
|
+
import { describe, expect, it } from 'vitest';
|
|
25
|
+
```
|
|
25
26
|
|
|
26
|
-
|
|
27
|
-
временем, о поведении, а не об устройстве:
|
|
27
|
+
## Один `describe` на функцию
|
|
28
28
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
29
|
+
Имя блока совпадает с именем функции дословно; заголовки `it` — предложения по-русски,
|
|
30
|
+
настоящим временем, о поведении, а не об устройстве:
|
|
31
|
+
|
|
32
|
+
```typescript
|
|
33
|
+
describe('applyDayClick', () => {
|
|
34
|
+
it('SC-BK-19 — клик по занятому дню ничего не меняет', () => {
|
|
35
|
+
expect(applyDayClick(day('2026-08-12'), selection)).toEqual(selection);
|
|
33
36
|
});
|
|
34
37
|
});
|
|
35
38
|
```
|
|
36
39
|
|
|
37
|
-
Идентификатор сценария из
|
|
38
|
-
отдельные
|
|
40
|
+
Идентификатор сценария из `docs/specs/<домен>/scenarios.md` стоит в начале заголовка, через
|
|
41
|
+
тире. Краевые случаи — отдельные `it` в том же блоке, а не один тест с десятком проверок.
|
|
39
42
|
|
|
40
|
-
## Фикстура собирается функцией с
|
|
43
|
+
## Фикстура собирается функцией с `Partial<T>`
|
|
41
44
|
|
|
42
|
-
Не повторяющимся литералом:
|
|
43
|
-
колонке правится в каждом месте — и в одном обязательно забывается.
|
|
45
|
+
Не повторяющимся литералом:
|
|
44
46
|
|
|
45
|
-
```
|
|
46
|
-
|
|
47
|
-
|
|
47
|
+
```typescript
|
|
48
|
+
function day(iso: string, overrides: Partial<ICalendarDay> = {}): ICalendarDay {
|
|
49
|
+
return {
|
|
50
|
+
iso,
|
|
51
|
+
dayOfMonth: Number(iso.slice(8)),
|
|
52
|
+
priceThb: 6000,
|
|
53
|
+
busyNight: isNightBusy(iso, BUSY),
|
|
54
|
+
past: false,
|
|
55
|
+
...overrides,
|
|
56
|
+
};
|
|
48
57
|
}
|
|
49
58
|
```
|
|
50
59
|
|
|
51
|
-
Общие константы лежат наверху файла, в области модуля.
|
|
60
|
+
Общие константы (`PRICING`, `BUSY`) лежат наверху файла, в области модуля.
|
|
52
61
|
|
|
53
62
|
## Решение выносится в чистую функцию
|
|
54
63
|
|
|
55
|
-
|
|
56
|
-
|
|
64
|
+
Господствующая форма в этом дереве: логика уезжает в `*.logic.ts`, `*.util.ts` или
|
|
65
|
+
`*.calculator.ts`, и проверяется вызовом — без `TestBed`, без подмены зависимостей. Образцы —
|
|
66
|
+
`libs/site/common/booking/util/src/lib/availability-calendar.logic.ts` и
|
|
67
|
+
`libs/api/<домен расчёта>/util/src/lib/quote.calculator.ts`.
|
|
57
68
|
|
|
58
|
-
##
|
|
69
|
+
## Процедура зовётся напрямую
|
|
59
70
|
|
|
60
|
-
Обработчик — метод
|
|
61
|
-
|
|
62
|
-
|
|
71
|
+
Обработчик — метод `handle` класса процедуры в слое `feature` своего домена. Двойник базы
|
|
72
|
+
пишется руками; образец — `FakePrismaClient` в
|
|
73
|
+
`libs/api/<домен заявок>/feature/src/lib/link-booking.procedure.spec.ts`:
|
|
74
|
+
|
|
75
|
+
```typescript
|
|
76
|
+
const prisma: FakePrismaClient = new FakePrismaClient();
|
|
77
|
+
await procedureWith(prisma, emitter).handle(request());
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Проверяются порядок действий, откат при отказе половины, идемпотентность повтора и то, что
|
|
81
|
+
именно ушло в базу. Раскладку полей стерегут тесты слоя `api`, и здесь она не повторяется.
|
|
63
82
|
|
|
64
83
|
## Разовый тест-доказательство
|
|
65
84
|
|
|
66
85
|
Дефект, который иначе подтверждается только чтением кода, доказывается тестом, написанным на
|
|
67
|
-
время разбора: он поднимает
|
|
86
|
+
время разбора: он поднимает настоящую процедуру, подменяет её единственный выход наружу и
|
|
68
87
|
считает походы.
|
|
69
88
|
|
|
70
|
-
|
|
71
|
-
|
|
89
|
+
```typescript
|
|
90
|
+
globalThis.fetch = (): Promise<Response> => Promise.resolve(Response.json({ success: false }));
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
Число до правки и число после — единственная форма ответа, которую такая проверка даёт: «сто
|
|
94
|
+
походов из ста» и «двадцать из ста» различимы, а «код выглядит правильным» — нет.
|
|
72
95
|
|
|
73
|
-
Второй способ снять «до» — вернуть на место версию файла из точки расхождения ветки и
|
|
74
|
-
тест ветки: падение теста и есть воспроизведение дефекта. Файл после этого
|
|
75
|
-
копией, и восстановление проверяется прогоном того же теста, а не памятью.
|
|
96
|
+
Второй способ снять «до» — вернуть на место версию файла из точки расхождения ветки и
|
|
97
|
+
прогнать тест ветки: падение теста и есть воспроизведение дефекта. Файл после этого
|
|
98
|
+
восстанавливается копией, и восстановление проверяется прогоном того же теста, а не памятью.
|
|
76
99
|
|
|
77
|
-
Двойники соседей не выдумываются: рабочий набор берётся из теста
|
|
100
|
+
Двойники соседей не выдумываются: рабочий набор берётся из теста соседней процедуры того же
|
|
78
101
|
домена. Двойник, собранный по типу, падает не на утверждении, а на вызове метода, которого у
|
|
79
102
|
него нет.
|
|
80
103
|
|
|
81
104
|
**Такой файл не коммитится.** Он живёт до конца разбора и удаляется вместе с ним; проверка,
|
|
82
|
-
которую стоит оставить, переписывается в
|
|
83
|
-
Признак временного файла — его заголовок не называет ни одного
|
|
105
|
+
которую стоит оставить, переписывается в обычный `*.spec.ts` с идентификатором сценария в
|
|
106
|
+
заголовке и едет в ветке. Признак временного файла — его заголовок не называет ни одного
|
|
107
|
+
сценария.
|
|
84
108
|
|
|
85
109
|
## Частые промахи
|
|
86
110
|
|
|
87
|
-
-
|
|
88
|
-
чем писать первый тест в либе, проверить, что конфиг рядом есть.
|
|
89
|
-
-
|
|
90
|
-
|
|
91
|
-
-
|
|
92
|
-
-
|
|
93
|
-
при этом остаются зелёными.
|
|
111
|
+
- Либа без своего `vitest.config.mts`: `nx test <проект>` пройдёт зелёным, не запустив ни
|
|
112
|
+
одного файла. Прежде чем писать первый тест в либе, проверить, что конфиг рядом есть.
|
|
113
|
+
- Подмена модуля (`vi.mock`) скрыла бы то, ради чего тест и заводится, — какой именно вызов
|
|
114
|
+
ушёл в базу и в каком порядке. Двойник пишется руками.
|
|
115
|
+
- Своих помощников для проверок не заводить: `expect(...).toBe(...)` и `.toEqual(...)` прямо.
|
|
116
|
+
- Заголовок с несуществующим идентификатором сценария роняет `npm run check:specs`, а сами
|
|
117
|
+
тесты при этом остаются зелёными.
|
|
@@ -2,12 +2,13 @@
|
|
|
2
2
|
name: translations-key
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: translations
|
|
5
|
-
description: Паттерн правила translations. Брать, когда в интерфейсе появляется видимый текст — куда положить ключ, как подставить его в разметку и в класс, чем дозаполнить остальные
|
|
5
|
+
description: Паттерн правила translations. Брать, когда в интерфейсе появляется видимый текст — куда положить ключ, как подставить его в разметку и в класс, чем дозаполнить остальные семь локалей и чем проверить полноту. Не брать для перевода контента объекта — его заполняет бэкенд.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Ключ перевода
|
|
9
9
|
|
|
10
|
-
Паттерн правила `translations`. Что при этом должно быть верно — закон
|
|
10
|
+
Паттерн правила `translations`. Что при этом должно быть верно — закон
|
|
11
|
+
`docs/constitution/application/locales.md`.
|
|
11
12
|
|
|
12
13
|
## Когда брать
|
|
13
14
|
|
|
@@ -16,36 +17,48 @@ description: Паттерн правила translations. Брать, когда
|
|
|
16
17
|
|
|
17
18
|
## Куда кладётся ключ
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
20
|
+
`libs/common/i18n/src/lib/dictionaries/<локаль>/<раздел>.json`. Разделов четыре, и наборы
|
|
21
|
+
ключей сверяются внутри раздела:
|
|
22
|
+
|
|
23
|
+
| Раздел | Что в нём |
|
|
24
|
+
| ------------- | --------------------------- |
|
|
25
|
+
| `common.json` | общее для сайта и админки |
|
|
26
|
+
| `site.json` | публичный сайт |
|
|
27
|
+
| `admin.json` | админ-панель |
|
|
28
|
+
| `mail.json` | письма и документ-основание |
|
|
29
|
+
|
|
30
|
+
Локалей восемь: `en`, `ru`, `de`, `zh-Hans`, `zh-Hant`, `ko`, `th`, `hi`. Ключ заводится во
|
|
31
|
+
всех — пустое значение считается пропуском, а не переводом.
|
|
22
32
|
|
|
23
33
|
## Подстановка
|
|
24
34
|
|
|
25
|
-
В разметке —
|
|
35
|
+
В разметке — пайпом:
|
|
26
36
|
|
|
27
37
|
```html
|
|
28
|
-
<h1 rtElem="title">{{ '
|
|
29
|
-
|
|
38
|
+
<h1 rtElem="title">{{ 'bookingsTitle' | transloco }}</h1>
|
|
39
|
+
<<префикс>-table [emptyMessage]="'bookingsEmpty' | transloco" [ariaLabel]="'bookingsTableAria' | transloco">
|
|
30
40
|
```
|
|
31
41
|
|
|
42
|
+
В классе — сигналом:
|
|
43
|
+
|
|
32
44
|
```typescript
|
|
33
|
-
protected readonly title: Signal<string> = translateSignal('
|
|
45
|
+
protected readonly title: Signal<string> = translateSignal('promoCodesTitle');
|
|
34
46
|
```
|
|
35
47
|
|
|
36
48
|
## Дозаполнить и проверить
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
50
|
+
```bash
|
|
51
|
+
npm run i18n:fill # дозаполняет недостающее в остальных локалях
|
|
52
|
+
npx nx test common-i18n # роняет сборку на недостающем или пустом ключе
|
|
53
|
+
```
|
|
40
54
|
|
|
41
55
|
## Частые промахи
|
|
42
56
|
|
|
43
|
-
-
|
|
44
|
-
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
- **Ключ положен не в свой раздел:** наборы сверяются внутри раздела, и расхождение вылезет как
|
|
57
|
+
- Текст строкой прямо в шаблоне: он уедет в интерфейс по-английски во всех локалях перевода.
|
|
58
|
+
- Ключ заведён только в `en` и `ru`: тест полноты падает, но замечают это уже в гейте пуша.
|
|
59
|
+
- Пустая строка вместо перевода: на экране она выглядит как задуманная — кнопка без подписи,
|
|
60
|
+
заголовок без текста.
|
|
61
|
+
- Ключ положен не в свой раздел: наборы сверяются внутри раздела, и расхождение вылезет как
|
|
49
62
|
недостача в другом.
|
|
50
|
-
-
|
|
51
|
-
|
|
63
|
+
- Свой ключ успеха у панели правки записи: текст успеха принадлежит мутации, и заводится он во
|
|
64
|
+
всех локалях перевода сразу.
|
|
@@ -2,30 +2,30 @@
|
|
|
2
2
|
name: ts-procedure
|
|
3
3
|
kind: pattern
|
|
4
4
|
rule: typescript-conventions
|
|
5
|
-
description: Паттерн правила typescript-conventions. Брать при заведении или правке
|
|
5
|
+
description: Паттерн правила typescript-conventions. Брать при заведении или правке процедуры Connect на бэкенде — готовый класс с полем method и методом handle, зависимости конструктором, имя файла и класса, почему форма именно такая. Не брать для объявления доступа к процедуре — это паттерн permissions-procedure.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
#
|
|
8
|
+
# Процедура Connect
|
|
9
9
|
|
|
10
10
|
Паттерн правила `typescript-conventions`. Что при этом должно быть верно — закон
|
|
11
|
-
`
|
|
11
|
+
`docs/constitution/code-structure.md`.
|
|
12
12
|
|
|
13
13
|
## Когда брать
|
|
14
14
|
|
|
15
|
-
- Заводится
|
|
16
|
-
- Правится тело
|
|
15
|
+
- Заводится новая процедура бэкенда.
|
|
16
|
+
- Правится тело существующей.
|
|
17
17
|
- Домен переезжает на вертикальную нарезку.
|
|
18
18
|
|
|
19
|
-
##
|
|
19
|
+
## Одна процедура — один класс
|
|
20
20
|
|
|
21
|
-
Файл
|
|
22
|
-
|
|
23
|
-
|
|
21
|
+
Файл `<процедура>.procedure.ts` в слое `feature` своего домена, класс `<Процедура>Procedure`,
|
|
22
|
+
публичное поле `method` с дескриптором из контракта и публичный метод `handle` с телом.
|
|
23
|
+
Зависимости приходят конструктором — на бэкенде DI нестовский, `inject()` там нет.
|
|
24
24
|
|
|
25
25
|
```typescript
|
|
26
26
|
@Injectable()
|
|
27
27
|
@ConnectProcedure()
|
|
28
|
-
@RequiresPermission('
|
|
28
|
+
@RequiresPermission('bookings:manage')
|
|
29
29
|
export class PingProcedure implements IConnectProcedure<typeof HealthService.method.ping> {
|
|
30
30
|
readonly #health: HealthCheckService;
|
|
31
31
|
|
|
@@ -45,22 +45,21 @@ export class PingProcedure implements IConnectProcedure<typeof HealthService.met
|
|
|
45
45
|
|
|
46
46
|
## Почему форма такая
|
|
47
47
|
|
|
48
|
-
Прежняя —
|
|
49
|
-
недостижимым для спеки: наружу торчал только
|
|
50
|
-
заглушает каждый непереданный метод ответом
|
|
51
|
-
мог обслуживаться двумя доменами.
|
|
48
|
+
Прежняя — `register(router)` с телами в замыканиях внутри `router.service(...)` — делала
|
|
49
|
+
обработчик недостижимым для спеки: наружу торчал только класс с методом `register`. А
|
|
50
|
+
`router.service` заглушает каждый непереданный метод ответом `Unimplemented`, поэтому один
|
|
51
|
+
proto-сервис не мог обслуживаться двумя доменами.
|
|
52
52
|
|
|
53
|
-
Класс решает и то, и другое:
|
|
54
|
-
|
|
53
|
+
Класс решает и то, и другое: `handle` зовётся спекой напрямую, а реестр кладёт процедуры
|
|
54
|
+
поштучно через `router.rpc`.
|
|
55
55
|
|
|
56
56
|
## Частые промахи
|
|
57
57
|
|
|
58
|
-
-
|
|
59
|
-
и новых таких файлов не заводится.
|
|
60
|
-
-
|
|
61
|
-
-
|
|
62
|
-
-
|
|
63
|
-
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
и с выписанным руками не сходится.
|
|
58
|
+
- Третий суффикс: `*.rpc.ts` и `*.connect.ts` — прежние имена, они уходят вместе с последним
|
|
59
|
+
переехавшим доменом, и новых таких файлов не заводится.
|
|
60
|
+
- `inject()` в классе процедуры: на бэкенде зависимости идут конструктором.
|
|
61
|
+
- Тело в замыкании внутри регистрации роутера: спека до него не дотянется.
|
|
62
|
+
- Процедура без объявления доступа: приложение не поднимется.
|
|
63
|
+
- Приведение `as` в переводе моделей: на бэкенде оно запрещено так же, как в маппере фронта.
|
|
64
|
+
- Свой тип у результата `groupBy` Prisma: он условный, собирается из аргументов вызова и с
|
|
65
|
+
выписанным руками не сходится. Там, где ключей единицы, идёт `count` на ключ в `Promise.all`.
|
|
@@ -2,51 +2,70 @@
|
|
|
2
2
|
name: angular-patterns
|
|
3
3
|
kind: rule
|
|
4
4
|
law: frontend-application
|
|
5
|
-
description: Правило под
|
|
5
|
+
description: Правило под «Закон о фронтовом приложении». Брать при правке любого класса Angular — компонента, стора админки, сервиса, директивы, пайпа, гарда, интерцептора. Называет сигнальный API входов, OnPush, zoneless, inject и место, где живёт подписка. Не действует под libs/api и apps/api. Готовый код — в паттерне angular-patterns-state.
|
|
6
6
|
---
|
|
7
7
|
|
|
8
|
-
# Реактивность экрана —
|
|
8
|
+
# Реактивность экрана — как это устроено здесь
|
|
9
9
|
|
|
10
|
-
Правило под закон `
|
|
11
|
-
здесь —
|
|
12
|
-
|
|
13
|
-
`component-structure`, оформление — `styling-bem`, окружение браузера — `platform-access`, слой
|
|
10
|
+
Правило под закон `docs/constitution/frontend-application.md`. Закон говорит, что должно быть
|
|
11
|
+
верно; здесь — на чём это стоит в этом дереве. Раскладка файла компонента —
|
|
12
|
+
`component-structure`, стили — `styling-bem`, окружение браузера — `platform-access`, слой
|
|
14
13
|
обращения к серверу — `api-layer`. Все пять под одним законом.
|
|
15
14
|
|
|
16
|
-
Правило про фронт:
|
|
17
|
-
|
|
15
|
+
Правило про фронт: `libs/api/**` и `apps/api/**` — это NestJS, там своя среда, и ничего из
|
|
16
|
+
перечисленного не применяется.
|
|
18
17
|
|
|
19
|
-
##
|
|
18
|
+
## Как это называется здесь
|
|
20
19
|
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
| В законе | Здесь |
|
|
21
|
+
| --------------------------------------- | -------------------------------------------------------------------------------------------------- |
|
|
22
|
+
| состояние, которое пересчитывается само | `signal()` и `computed()`; Angular без Zone.js (`provideZonelessChangeDetection()`) |
|
|
23
|
+
| вход и выход компонента | `input()`, `input.required()`, `output()`; `viewChild()`, `contentChild()` и их множественные пары |
|
|
24
|
+
| перерисовка по требованию | `ChangeDetectionStrategy.OnPush` — на каждом компоненте |
|
|
25
|
+
| владелец подписки | `takeUntilDestroyed(this.#destroyRef)` |
|
|
26
|
+
| источник действия | `Subject` с суффиксом `Source` в имени поля |
|
|
23
27
|
|
|
24
|
-
##
|
|
28
|
+
## Где это лежит
|
|
25
29
|
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
30
|
+
В этом дереве — таблица в `implementation.md` рядом. Пути живут там, а не здесь: правило
|
|
31
|
+
переносится между репозиториями, раскладка — нет, и путь, названный в правиле, врёт в первом
|
|
32
|
+
же дереве, которое держит код иначе.
|
|
33
|
+
|
|
34
|
+
## Как закон применяется здесь
|
|
35
|
+
|
|
36
|
+
- **Подписка объявляется один раз, а не в методе действия.** Метод толкает значение в
|
|
37
|
+
источник, а долгоживущая подписка со `switchMap`, `exhaustMap` или `concatMap` объявляется в
|
|
38
|
+
конструкторе, в `ngOnInit` или в инициализаторе поля.
|
|
39
|
+
- **Подписка гасится вместе с владельцем.** `takeUntilDestroyed` ставится в тот же поток, где
|
|
40
|
+
объявлена подписка.
|
|
41
|
+
- **Источник действия носит суффикс `Source` в имени.** Иначе поток и значение в коде
|
|
42
|
+
неотличимы, и `next` уходит не туда.
|
|
33
43
|
- **Списочный стор наследует общую основу.** Записи, страница, порядок, условия отбора, строка
|
|
34
|
-
поиска и
|
|
44
|
+
поиска и конфиг выборки уже там, и наследнику остаются четыре строки.
|
|
45
|
+
|
|
46
|
+
## Чего из закона здесь нет
|
|
47
|
+
|
|
48
|
+
Ни `OnPush`, ни сигнальный API входов, ни отсутствие геттеров в компонентах не проверяет
|
|
49
|
+
ничто: `@Input()` и геттер компилируются и работают, а расхождение видно только чтением.
|
|
50
|
+
Обращение к окружению браузера тоже не проверяется — это `Q-FA-1` в законе.
|
|
35
51
|
|
|
36
52
|
## Паттерны
|
|
37
53
|
|
|
38
|
-
- `angular-patterns-state` —
|
|
54
|
+
- `angular-patterns-state` — сигналы, производные значения, состояние сервиса, подписка.
|
|
39
55
|
|
|
40
56
|
## Ловушки
|
|
41
57
|
|
|
42
|
-
- **Производное значение
|
|
43
|
-
|
|
58
|
+
- **Производное значение считается `computed`, а не эффектом.** `effect`, кладущий значение в
|
|
59
|
+
сигнал, — это ручной пересчёт, и он рано или поздно отстаёт от источника.
|
|
44
60
|
- **Геттеров в компонентах нет.** Геттер пересчитывается на каждой перерисовке, и цена его не
|
|
45
61
|
видна ни в одном месте кода.
|
|
46
62
|
- **Подписка на каждый вызов метода не даёт выбрать, что делать с предыдущим запросом.**
|
|
47
63
|
Быстрые нажатия дают гонку ответов, и побеждает тот, что вернулся последним, а не тот, что
|
|
48
64
|
нажали последним.
|
|
49
|
-
-
|
|
50
|
-
считается подпиской, а
|
|
51
|
-
|
|
52
|
-
|
|
65
|
+
- Запрет подписки в методе идёт по имени `subscribe`, а не по типу: вызов с таким именем у
|
|
66
|
+
чего угодно считается подпиской, а `const fn = stream$.subscribe` без вызова — нет.
|
|
67
|
+
Разрешены конструктор, `ngOnInit`, инициализатор поля и всё, что объявлено вне класса;
|
|
68
|
+
запрещены остальные методы, включая приватные с `#`, геттеры и `ngAfterViewInit`.
|
|
69
|
+
- Инициализация DOM после первой отрисовки — `afterNextRender()`, а не `ngAfterViewInit`:
|
|
70
|
+
сайт отдаётся сервером, и DOM там появляется позже.
|
|
71
|
+
- `viewChild` на поле с `#` Angular не принимает — поле объявляется `protected`.
|