Merge branch 'review/slice-1-recheck'
This commit is contained in:
@@ -2,6 +2,53 @@
|
|||||||
|
|
||||||
Сводка: [`../reviewed.md`](../reviewed.md). Индекс среза: [`README.md`](README.md).
|
Сводка: [`../reviewed.md`](../reviewed.md). Индекс среза: [`README.md`](README.md).
|
||||||
|
|
||||||
|
## Перепроверка среза 1 (после одежды / сессии / общения)
|
||||||
|
|
||||||
|
- **Фазы:** 0–4
|
||||||
|
- **Проверен на:** `f6bb4c7`, 2026-08-20
|
||||||
|
- **Пути:** `src/HSchool.Client`, `src/HSchool.Protocol`, `src/HSchool.Content`,
|
||||||
|
`src/HSchool.Server/Game`, `src/HSchool.Server/Api/ModEndpoints.cs`,
|
||||||
|
`src/HSchool.Server/Api/SchoolEndpoints.cs`, `src/HSchool.Server/Net`,
|
||||||
|
`tests/HSchool.Content.Tests`, `tests/HSchool.Protocol.Tests`,
|
||||||
|
`tests/HSchool.AppHost.Tests/SchoolApiTests.cs`, `tests/HSchool.AppHost.Tests/GameSocketTests.cs`,
|
||||||
|
`tests/HSchool.AppHost.Tests/ServiceabilityTests.cs`
|
||||||
|
- **Итог:** перепроверка; `git log 418a4ea..HEAD` по путям среза непустой (одежда, сессия, общение,
|
||||||
|
ремесло). Обещания фаз 0–4 целы. Дописаны 3 теста. Код не чинился. Фазы 60 и 61 не закрывались.
|
||||||
|
|
||||||
|
Что подтверждено (после дрейфа):
|
||||||
|
|
||||||
|
- PixiJS нет в `package.json` и в клиентском коде. Дерево карты, пять секций локации, клиентский
|
||||||
|
фильтр, `localize()`. Оболочка — вкладки Карта/Люди; отказ от ленты событий в фазе 1 и
|
||||||
|
`design/near-term.md`.
|
||||||
|
- `SchoolWorker` — `LongRunning`, свой `World`, аккумулятор `MaxCatchUpSteps = 5`. Супервизор
|
||||||
|
таблицу держит и `World` не трогает (фаза 58 разрезала файл, не поток). Сейв на
|
||||||
|
create/shutdown/`SaveInterval`; `ReloadFromDisk_RestoresAPausedClock`,
|
||||||
|
`PausingOneSchool_DoesNotStopAnother`.
|
||||||
|
- Каталог/карта: JSONC, last-wins, parent/abstract, патчи, связность, пустая комната, отказ без
|
||||||
|
двора, ванильная раскладка. Нет папки мода — школу не стартовать, файл не трогать
|
||||||
|
(`SchoolContentUnavailableException`, `persist: false`, `RememberIncompatible`).
|
||||||
|
- `GET /api/mods`, каталог `?lang=`, create с картой и модами, снимок при OpenSchool. `core` в
|
||||||
|
диалоге нельзя снять. Кодек, `protocol.ts` и `docs/protocol.md` — один `MapSnapshot` (протокол v9,
|
||||||
|
бамп поздних срезов, раскладка снимка та же).
|
||||||
|
- `modal.ts` резолвит промис явно; `.dialog--screen` — `height: fit-content`;
|
||||||
|
`DefaultStartDate` UTC; `MapSnapshotSize` от сообщения; outbox `DropOldest`;
|
||||||
|
`WaitToReadAsync` переносится между итерациями; меню патчит `card.update`.
|
||||||
|
|
||||||
|
Дописано на этом проходе:
|
||||||
|
|
||||||
|
- `gameScreen.test.ts` — клик по дереву фильтрует панель локации на клиенте
|
||||||
|
- `gameScreen.test.ts` — `localize()` обновляет пустые секции локации
|
||||||
|
- `ServiceabilityTests.MissingModFolder_LeavesTheSchoolUnstartedAndTheFileUntouched`
|
||||||
|
|
||||||
|
Исправлено:
|
||||||
|
|
||||||
|
- ничего: расхождений с дизайном среза 1, которые надо было чинить кодом, не нашлось.
|
||||||
|
|
||||||
|
Открытое:
|
||||||
|
|
||||||
|
- срезы 2–6 в сводке всё ещё на старых коммитах — дрейф после одежды/сессии/общения; не этот проход.
|
||||||
|
- фазы 60 и 61 (портреты) в работе; ревью их не закрывало.
|
||||||
|
|
||||||
## Срез 1. Оболочка и карта
|
## Срез 1. Оболочка и карта
|
||||||
|
|
||||||
- **Фазы:** 0–4
|
- **Фазы:** 0–4
|
||||||
|
|||||||
@@ -20,7 +20,7 @@
|
|||||||
|
|
||||||
| Срез | Проверен на | Журнал |
|
| Срез | Проверен на | Журнал |
|
||||||
| --- | --- | --- |
|
| --- | --- | --- |
|
||||||
| Срез 1. Оболочка и карта | `418a4ea` | [01-shell](01-shell/reviewed.md) |
|
| Срез 1. Оболочка и карта | `f6bb4c7` | [01-shell](01-shell/reviewed.md) |
|
||||||
| Срез 2. Люди | `bdb1253` | [02-people](02-people/reviewed.md) |
|
| Срез 2. Люди | `bdb1253` | [02-people](02-people/reviewed.md) |
|
||||||
| Срез 3. Штат | `bdb1253` | [03-staffing](03-staffing/reviewed.md) |
|
| Срез 3. Штат | `bdb1253` | [03-staffing](03-staffing/reviewed.md) |
|
||||||
| Срез 4. Расписание | `bdb1253` | [04-schedule](04-schedule/reviewed.md) |
|
| Срез 4. Расписание | `bdb1253` | [04-schedule](04-schedule/reviewed.md) |
|
||||||
|
|||||||
@@ -98,6 +98,82 @@ describe('GameScreen speed buttons', () => {
|
|||||||
});
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
|
describe('GameScreen location tree', () => {
|
||||||
|
it('filters the location panel on the client when a tree node is clicked', () => {
|
||||||
|
const screen = new GameScreen({
|
||||||
|
onLeave: () => {},
|
||||||
|
onSetRunning: () => {},
|
||||||
|
onSetSpeed: () => {},
|
||||||
|
onSkip: () => {},
|
||||||
|
});
|
||||||
|
document.body.append(screen.element);
|
||||||
|
screen.show(school());
|
||||||
|
screen.applyMap(1, [
|
||||||
|
{
|
||||||
|
kind: 0,
|
||||||
|
id: 'yard',
|
||||||
|
parentId: '',
|
||||||
|
name: 'Двор',
|
||||||
|
pupilSlots: 0,
|
||||||
|
items: [],
|
||||||
|
positions: [],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
kind: 3,
|
||||||
|
id: 'office',
|
||||||
|
parentId: 'yard',
|
||||||
|
name: 'Кабинет',
|
||||||
|
pupilSlots: 0,
|
||||||
|
items: [{ name: 'Стул', count: 2 }],
|
||||||
|
positions: ['Директор'],
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
expect(screen.element.querySelector('.panel__name')?.textContent).toBe('Двор');
|
||||||
|
const itemsSection = screen.element.querySelector('.panel__section');
|
||||||
|
expect(itemsSection?.querySelector('.panel__empty')?.textContent).toBe(t('itemsEmpty'));
|
||||||
|
|
||||||
|
const office = [...screen.element.querySelectorAll<HTMLButtonElement>('.tree__button')].find(
|
||||||
|
(button) => button.textContent === 'Кабинет',
|
||||||
|
);
|
||||||
|
expect(office).toBeTruthy();
|
||||||
|
office?.click();
|
||||||
|
|
||||||
|
expect(screen.element.querySelector('.panel__name')?.textContent).toBe('Кабинет');
|
||||||
|
expect(office?.classList.contains('tree__button--active')).toBe(true);
|
||||||
|
const lists = [...screen.element.querySelectorAll('.panel__section .panel__list')].map(
|
||||||
|
(list) => [...list.querySelectorAll('li')].map((item) => item.textContent ?? ''),
|
||||||
|
);
|
||||||
|
expect(lists.some((entries) => entries.includes('Стул ×2'))).toBe(true);
|
||||||
|
expect(lists.some((entries) => entries.includes('Директор'))).toBe(true);
|
||||||
|
});
|
||||||
|
|
||||||
|
it('relocalizes empty location sections when the language switches', () => {
|
||||||
|
setLocale('en');
|
||||||
|
const screen = new GameScreen({
|
||||||
|
onLeave: () => {},
|
||||||
|
onSetRunning: () => {},
|
||||||
|
onSetSpeed: () => {},
|
||||||
|
onSkip: () => {},
|
||||||
|
});
|
||||||
|
document.body.append(screen.element);
|
||||||
|
screen.show(school());
|
||||||
|
|
||||||
|
expect(screen.element.querySelector('.panel__section-title')?.textContent).toBe(t('locationItems'));
|
||||||
|
expect([...screen.element.querySelectorAll('.panel__empty')].map((node) => node.textContent)).toContain(
|
||||||
|
t('itemsEmpty'),
|
||||||
|
);
|
||||||
|
|
||||||
|
setLocale('ru');
|
||||||
|
screen.localize();
|
||||||
|
|
||||||
|
expect(screen.element.querySelector('.panel__section-title')?.textContent).toBe(t('locationItems'));
|
||||||
|
expect([...screen.element.querySelectorAll('.panel__empty')].map((node) => node.textContent)).toContain(
|
||||||
|
t('itemsEmpty'),
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
describe('GameScreen location talk', () => {
|
describe('GameScreen location talk', () => {
|
||||||
it('shows talk partners from the directory on the location list, not from the frame', () => {
|
it('shows talk partners from the directory on the location list, not from the frame', () => {
|
||||||
setLocale('ru');
|
setLocale('ru');
|
||||||
|
|||||||
@@ -83,6 +83,51 @@ public class ServiceabilityTests(AppHostFixture fixture)
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task MissingModFolder_LeavesTheSchoolUnstartedAndTheFileUntouched()
|
||||||
|
{
|
||||||
|
using var client = fixture.App.CreateHttpClient("server");
|
||||||
|
await SchoolApiTests.ResetAsync(client);
|
||||||
|
var created = await SchoolApiTests.CreateAsync(client, "Без папки мода", TuesdayMorning);
|
||||||
|
var directory = await SavesDirectoryAsync(client);
|
||||||
|
var path = Path.Combine(directory, $"{created.Id}.json");
|
||||||
|
var node = JsonNode.Parse(File.ReadAllText(path))
|
||||||
|
?? throw new InvalidOperationException($"Save {path} parsed to nothing.");
|
||||||
|
var mods = node["modIds"] as JsonArray
|
||||||
|
?? throw new InvalidOperationException($"Save {path} has no modIds.");
|
||||||
|
mods.Add("missing-pack-slice1-recheck");
|
||||||
|
var patched = node.ToJsonString(new JsonSerializerOptions { WriteIndented = true });
|
||||||
|
|
||||||
|
try
|
||||||
|
{
|
||||||
|
await SchoolApiTests.ResetAsync(client);
|
||||||
|
File.WriteAllText(path, patched);
|
||||||
|
var before = File.ReadAllBytes(path);
|
||||||
|
|
||||||
|
using var reload = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
||||||
|
reload.EnsureSuccessStatusCode();
|
||||||
|
|
||||||
|
Assert.Equal(before, File.ReadAllBytes(path));
|
||||||
|
|
||||||
|
var state = await SchoolApiTests.GetSchoolsAsync(client);
|
||||||
|
var mine = state.Schools.FirstOrDefault(school => school.Id == created.Id);
|
||||||
|
var other = state.Others.FirstOrDefault(school => school.Id == created.Id);
|
||||||
|
Assert.True(mine is not null || other is not null);
|
||||||
|
Assert.False(mine?.Running ?? other?.Running ?? true);
|
||||||
|
|
||||||
|
using var people = await client.GetAsync(
|
||||||
|
$"/api/schools/{created.Id}/people?pageSize=1",
|
||||||
|
TestContext.Current.CancellationToken);
|
||||||
|
Assert.Equal(HttpStatusCode.NotFound, people.StatusCode);
|
||||||
|
}
|
||||||
|
finally
|
||||||
|
{
|
||||||
|
DeleteSchoolFiles(directory, created.Id);
|
||||||
|
using var cleanup = await client.PostAsync("/api/dev/reload-schools", content: null, TestContext.Current.CancellationToken);
|
||||||
|
cleanup.EnsureSuccessStatusCode();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task Dump_ReturnsPeopleNodesAndTimetable()
|
public async Task Dump_ReturnsPeopleNodesAndTimetable()
|
||||||
{
|
{
|
||||||
|
|||||||
Reference in New Issue
Block a user