Files
convy/internal/project/project_test.go
T
av 23d88c4048 проектные команды и ссылки на источник
- заведён internal/source: уровни ссылаются друг на друга путём на диске
  или git-репозиторием, ревизия закрепляется хвостом #ref; клон делается
  заново и удаляется, кэша нет
- добавлены init, add, pull, list, check в проекте — манифест
  .conventions.toml, сборка копий по разу на компонент, маркер локальной
  части, READING.md рядом
- проверки формы развязаны с набором: принимают lang.Vocabulary, а язык
  копии узнаётся по строке о версии — манифеста рядом с ней нет
2026-07-27 20:42:18 +03:00

134 lines
4.0 KiB
Go

package project_test
import (
"strings"
"testing"
"git.vakhrushev.me/av/convy/internal/doc"
"git.vakhrushev.me/av/convy/internal/lang"
"git.vakhrushev.me/av/convy/internal/project"
)
const versionLine = `Ключевые слова ДОЛЖЕН, НЕ ДОЛЖЕН, СЛЕДУЕТ, НЕ СЛЕДУЕТ, ДОПУСКАЕТСЯ и метки
ПОЧЕМУ, ПРИМЕРЫ, МЕХАНИЗИРОВАНО и СНЯТО толкуются как описано в языке
конвенций версии 1 — тогда и только тогда, когда написаны заглавными.`
const baseLayer = `---
topic: time
prefix: TIME
---
# Время
Как приложение записывает моменты.
` + versionLine + `
## Правила
### TIME-1. Момент записывается в UTC
**ДОЛЖЕН.** Момент времени записывается с суффиксом Z.
**ПОЧЕМУ.** Без явного смещения не видно, в какой зоне запись сделана.
`
const goLayer = `---
topic: time
prefix: GTIM
lang: go
extends: arch/time.md
---
# Время: реализация на Go
Как базовый слой выполняется в Go-коде.
` + versionLine + `
## Правила
### GTIM-1. «Сейчас» берётся у слоя хранилища
**ДОЛЖЕН.** Текущее время приходит из ` + "`store.Now()`" + `.
**ПОЧЕМУ.** Единая точка даёт гарантированный UTC.
`
func layers(t *testing.T, bodies ...string) []*doc.Document {
t.Helper()
v, err := lang.Lookup(1, "ru")
if err != nil {
t.Fatal(err)
}
var out []*doc.Document
for i, body := range bodies {
d, err := doc.Parse("layer.md", body)
if err != nil {
t.Fatalf("layer %d: %v", i, err)
}
d.Blocks(v)
out = append(out, d)
}
return out
}
func vocab(t *testing.T) lang.Vocabulary {
t.Helper()
v, err := lang.Lookup(1, "ru")
if err != nil {
t.Fatal(err)
}
return v
}
// A copy is one document rather than three files glued together: the first
// layer is the document, and every layer after it is a section of it.
func TestRenderMakesTheLayersSectionsOfOneDocument(t *testing.T) {
body := project.Render("time", layers(t, baseLayer, goLayer), vocab(t))
for _, want := range []string{
"---\norigin: time\n---",
"\n# Время\n",
"\n## Правила\n",
"\n### TIME-1. Момент записывается в UTC\n",
"\n## Время: реализация на Go\n",
"\n### Правила\n",
"\n#### GTIM-1. «Сейчас» берётся у слоя хранилища\n",
} {
if !strings.Contains(body, want) {
t.Errorf("the copy lacks %q:\n%s", want, body)
}
}
if strings.Contains(body, "topic: time\nprefix:") {
t.Errorf("the front matter of a layer travelled into the copy:\n%s", body)
}
if strings.Contains(body, "extends:") {
t.Errorf("a path of the suite travelled into the copy:\n%s", body)
}
}
// The line about the language is what makes a copy readable on its own, and
// saying it three times over says nothing more than saying it once.
func TestRenderNamesTheLanguageOnce(t *testing.T) {
body := project.Render("time", layers(t, baseLayer, goLayer), vocab(t))
if n := strings.Count(body, "толкуются как описано"); n != 1 {
t.Errorf("the language version line appears %d times:\n%s", n, body)
}
if !strings.Contains(body, "конвенций версии 1") {
t.Errorf("the language version line is gone altogether:\n%s", body)
}
}
// A single layer is not a special case: assembly out of one layer is a copy of
// it, with no section headings invented around it.
func TestRenderOfOneLayerKeepsItsLevels(t *testing.T) {
body := project.Render("time", layers(t, baseLayer), vocab(t))
if !strings.Contains(body, "\n# Время\n") || !strings.Contains(body, "\n### TIME-1.") {
t.Errorf("a single layer was restructured:\n%s", body)
}
if strings.Contains(body, "####") {
t.Errorf("headings of a single layer were demoted:\n%s", body)
}
}