feat(meta-info): выводить «Представление типа» для ссылочных объектов

В шапке ссылочных объектов (справочники, документы, перечисления, ПВХ/ПВР,
планы счетов, планы обмена, бизнес-процессы, задачи) теперь выводится строка
«Представление типа» — имя ссылочного типа в диалогах выбора типа, с fallback
ObjectPresentation -> Synonym -> Name. В режиме full дополнительно выводятся
заданные сырые представления (объекта/списка и расширенные).

Тесты: раннер принимает stdoutContains строкой или массивом, добавлен
stdoutNotContains. Добавлены кейсы meta-info (ед.ч. ПВХ, full со всеми
представлениями, fallback на синоним) и негативная проверка у регистра.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
This commit is contained in:
Nick Shirokov
2026-06-01 15:40:01 +03:00
parent 7c9769c644
commit b188d338f9
8 changed files with 131 additions and 9 deletions
+28 -1
View File
@@ -1,4 +1,4 @@
# meta-info v1.1 — Compact summary of 1C metadata object
# meta-info v1.2 — Compact summary of 1C metadata object
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
param(
[Parameter(Mandatory=$true)][Alias('Path')][string]$ObjectPath,
@@ -422,6 +422,22 @@ $objName = $props.SelectSingleNode("md:Name", $ns).InnerText
$synNode = $props.SelectSingleNode("md:Synonym", $ns)
$synonym = Get-MLText $synNode
# Presentations (type-choice dialogs show "Представление объекта" as the ref type name)
$objPresentation = Get-MLText $props.SelectSingleNode("md:ObjectPresentation", $ns)
$extObjPresentation = Get-MLText $props.SelectSingleNode("md:ExtendedObjectPresentation", $ns)
$listPresentation = Get-MLText $props.SelectSingleNode("md:ListPresentation", $ns)
$extListPresentation = Get-MLText $props.SelectSingleNode("md:ExtendedListPresentation", $ns)
# Reference (ref-typed) metadata objects — those with a ...Ref type
$refMdTypes = @("Catalog","Document","Enum","ChartOfAccounts","ChartOfCharacteristicTypes",
"ChartOfCalculationTypes","ExchangePlan","BusinessProcess","Task")
$isRefObject = $refMdTypes -contains $mdType
# Effective type presentation: ObjectPresentation -> Synonym -> Name
$typePresentation = if ($objPresentation) { $objPresentation }
elseif ($synonym) { $synonym }
else { $objName }
# --- Handle -Name drill-down ---
$drillDone = $false
if ($Name -and $childObjs) {
@@ -593,6 +609,17 @@ if (-not $drillDone) {
$header += " ==="
Out $header
# --- Type presentation (ref objects) ---
if ($isRefObject) {
Out "Представление типа: $typePresentation"
if ($Mode -eq "full") {
if ($objPresentation) { Out "Представление объекта: $objPresentation" }
if ($extObjPresentation) { Out "Расширенное представление объекта: $extObjPresentation" }
if ($listPresentation) { Out "Представление списка: $listPresentation" }
if ($extListPresentation) { Out "Расширенное представление списка: $extListPresentation" }
}
}
# --- Mode: brief ---
if ($Mode -eq "brief") {
# Attributes
+29 -1
View File
@@ -1,4 +1,4 @@
# meta-info v1.1 — Compact summary of 1C metadata object (Python port)
# meta-info v1.2 — Compact summary of 1C metadata object (Python port)
# Source: https://github.com/Nikolay-Shirokov/cc-1c-skills
import argparse
import os
@@ -477,6 +477,21 @@ obj_name = inner_text(find(props, "md:Name"))
syn_node = find(props, "md:Synonym")
synonym = get_ml_text(syn_node)
# Presentations (type-choice dialogs show "Представление объекта" as the ref type name)
obj_presentation = get_ml_text(find(props, "md:ObjectPresentation"))
ext_obj_presentation = get_ml_text(find(props, "md:ExtendedObjectPresentation"))
list_presentation = get_ml_text(find(props, "md:ListPresentation"))
ext_list_presentation = get_ml_text(find(props, "md:ExtendedListPresentation"))
# Reference (ref-typed) metadata objects — those with a ...Ref type
ref_md_types = {"Catalog", "Document", "Enum", "ChartOfAccounts",
"ChartOfCharacteristicTypes", "ChartOfCalculationTypes",
"ExchangePlan", "BusinessProcess", "Task"}
is_ref_object = md_type in ref_md_types
# Effective type presentation: ObjectPresentation -> Synonym -> Name
type_presentation = obj_presentation or synonym or obj_name
# ── Handle -Name drill-down ──────────────────────────────────
drill_done = False
@@ -636,6 +651,19 @@ if not drill_done:
header += " ==="
out(header)
# Type presentation (ref objects)
if is_ref_object:
out(f"Представление типа: {type_presentation}")
if mode == "full":
if obj_presentation:
out(f"Представление объекта: {obj_presentation}")
if ext_obj_presentation:
out(f"Расширенное представление объекта: {ext_obj_presentation}")
if list_presentation:
out(f"Представление списка: {list_presentation}")
if ext_list_presentation:
out(f"Расширенное представление списка: {ext_list_presentation}")
if mode == "brief":
# Attributes
attrs = get_attributes(child_objs) if child_objs is not None else []
+15 -1
View File
@@ -108,6 +108,20 @@ node tests/skills/verify-snapshots.mjs --help # полный
`params` — параметры для навыка. Используются через `case.<field>` и `workPath` в `_skill.json`.
`expect.stdoutContains` / `expect.stdoutNotContains` — строка **или массив строк**. Каждая подстрока проверяется на наличие (`stdoutContains`) или отсутствие (`stdoutNotContains`) в stdout навыка. Удобно для info-навыков: проверить, что нужная строка есть, а лишней — нет.
```json
{
"name": "Представление типа у ПВХ",
"setup": "external:C:/WS/tasks/cfsrc/erp_8.3.24",
"params": { "objectPath": "ChartsOfCharacteristicTypes/ВидыСубконтоХозрасчетные" },
"expect": {
"stdoutContains": ["Представление типа: Вид субконто", "Представление объекта: Вид субконто"],
"stdoutNotContains": "Представление списка:"
}
}
```
### С дополнительными CLI-аргументами
```json
@@ -175,7 +189,7 @@ node tests/skills/verify-snapshots.mjs --help # полный
| `outputPath` | нет | Относительный путь для навыков с `-OutputPath` |
| `args_extra` | нет | Массив дополнительных CLI-аргументов |
| `preRun` | нет | Массив шагов подготовки (создание объектов и т.п.) |
| `expect` | нет | Дополнительные проверки: `files`, `stdoutContains` |
| `expect` | нет | Дополнительные проверки: `files`, `stdoutContains` (строка/массив), `stdoutNotContains` (строка/массив) |
| `expectError` | нет | `true` или строка — ожидается ошибка |
## Эталоны (snapshots)
@@ -0,0 +1,14 @@
{
"name": "Представления объекта/списка/расширенные в full — БП СтатьиДвиженияДенежныхСредств",
"setup": "external:C:/WS/tasks/cfsrc/acc_8.3.24",
"params": { "objectPath": "Catalogs/СтатьиДвиженияДенежныхСредств" },
"args_extra": ["-Mode", "full"],
"expect": {
"stdoutContains": [
"Представление типа: Статья движения ден. средств",
"Представление объекта: Статья движения ден. средств",
"Расширенное представление объекта: Статья движения денежных средств",
"Представление списка: Статьи движения денежных средств"
]
}
}
@@ -2,5 +2,8 @@
"name": "Реальный регистр бухгалтерии Хозрасчетный (БП)",
"setup": "external:C:/WS/tasks/cfsrc/acc_8.3.24",
"params": { "objectPath": "AccountingRegisters/Хозрасчетный" },
"expect": { "stdoutContains": "Хозрасчетный" }
"expect": {
"stdoutContains": "Хозрасчетный",
"stdoutNotContains": "Представление типа:"
}
}
@@ -0,0 +1,10 @@
{
"name": "Представление типа = синоним при пустом ObjectPresentation — ERP СтатьиБюджетов",
"setup": "external:C:/WS/tasks/cfsrc/erp_8.3.24",
"params": { "objectPath": "Catalogs/СтатьиБюджетов" },
"args_extra": ["-Mode", "full"],
"expect": {
"stdoutContains": "Представление типа: Статьи бюджетов",
"stdoutNotContains": "Представление объекта:"
}
}
@@ -0,0 +1,8 @@
{
"name": "Представление типа (ед.ч.) у ПВХ — ERP ВидыСубконтоХозрасчетные",
"setup": "external:C:/WS/tasks/cfsrc/erp_8.3.24",
"params": { "objectPath": "ChartsOfCharacteristicTypes/ВидыСубконтоХозрасчетные" },
"expect": {
"stdoutContains": "Представление типа: Вид субконто"
}
}
+23 -5
View File
@@ -588,8 +588,17 @@ async function runCaseAsync(testCase, opts) {
}
}
if (caseData.expect?.stdoutContains) {
if (!stdout.includes(caseData.expect.stdoutContains)) {
errors.push(`stdout does not contain "${caseData.expect.stdoutContains}"`);
const needles = Array.isArray(caseData.expect.stdoutContains)
? caseData.expect.stdoutContains : [caseData.expect.stdoutContains];
for (const needle of needles) {
if (!stdout.includes(needle)) errors.push(`stdout does not contain "${needle}"`);
}
}
if (caseData.expect?.stdoutNotContains) {
const needles = Array.isArray(caseData.expect.stdoutNotContains)
? caseData.expect.stdoutNotContains : [caseData.expect.stdoutNotContains];
for (const needle of needles) {
if (stdout.includes(needle)) errors.push(`stdout unexpectedly contains "${needle}"`);
}
}
if (errors.length === 0 && !caseData.expectError && !workspace.readOnly) {
@@ -754,10 +763,19 @@ function runCase(testCase, opts) {
}
}
// expect.stdoutContains
// expect.stdoutContains / stdoutNotContains (string or array)
if (caseData.expect?.stdoutContains) {
if (!stdout.includes(caseData.expect.stdoutContains)) {
errors.push(`stdout does not contain "${caseData.expect.stdoutContains}"`);
const needles = Array.isArray(caseData.expect.stdoutContains)
? caseData.expect.stdoutContains : [caseData.expect.stdoutContains];
for (const needle of needles) {
if (!stdout.includes(needle)) errors.push(`stdout does not contain "${needle}"`);
}
}
if (caseData.expect?.stdoutNotContains) {
const needles = Array.isArray(caseData.expect.stdoutNotContains)
? caseData.expect.stdoutNotContains : [caseData.expect.stdoutNotContains];
for (const needle of needles) {
if (stdout.includes(needle)) errors.push(`stdout unexpectedly contains "${needle}"`);
}
}