Record slice 3 recheck after later slices drifted staffing paths.
Pin homeroom seats, the management money row and assign-from-card, and drop the stale top-three skills claim from the protocol. Co-authored-by: Cursor <cursoragent@cursor.com>
This commit is contained in:
@@ -4,6 +4,7 @@
|
||||
import { afterEach, beforeEach, describe, expect, it, vi } from 'vitest';
|
||||
import {
|
||||
ApiError,
|
||||
assignSubject,
|
||||
fetchPerson,
|
||||
fetchGameStatus,
|
||||
fetchDressRules,
|
||||
@@ -11,6 +12,7 @@ import {
|
||||
fetchStaffing,
|
||||
fetchTimetable,
|
||||
hireStaff,
|
||||
unassignSubject,
|
||||
type PersonCard,
|
||||
type Staffing,
|
||||
type Timetable,
|
||||
@@ -356,3 +358,151 @@ describe('ManagementPanel rules tab', () => {
|
||||
expect(second.listElement.querySelector('.rules')?.hasAttribute('hidden')).toBe(false);
|
||||
});
|
||||
});
|
||||
|
||||
function hiredTeacher(): Staffing {
|
||||
return {
|
||||
allocated: 10_000,
|
||||
payroll: 4_000,
|
||||
remaining: 6_000,
|
||||
uncovered: [],
|
||||
applicants: [],
|
||||
staff: [
|
||||
{
|
||||
id: 't1',
|
||||
fullName: 'Petrov Ivan',
|
||||
female: false,
|
||||
age: 40,
|
||||
isParent: false,
|
||||
position: 'Teacher',
|
||||
positionLabel: 'Teacher',
|
||||
hourlyWageAsk: 50,
|
||||
weeklyHours: 20,
|
||||
monthlyPay: 4_000,
|
||||
subjects: [],
|
||||
},
|
||||
],
|
||||
positions: [{ defName: 'Teacher', label: 'Teacher' }],
|
||||
subjects: [
|
||||
{
|
||||
defName: 'Mathematics',
|
||||
label: 'Math',
|
||||
gradeMin: 5,
|
||||
gradeMax: 11,
|
||||
hoursPerWeek: 5,
|
||||
teachersShort: 1,
|
||||
},
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
describe('ManagementPanel money and subjects', () => {
|
||||
beforeEach(() => {
|
||||
sessionStorage.clear();
|
||||
setLocale('en');
|
||||
vi.mocked(fetchStaffing).mockReset();
|
||||
vi.mocked(fetchTimetable).mockReset();
|
||||
vi.mocked(fetchPerson).mockReset();
|
||||
vi.mocked(assignSubject).mockReset();
|
||||
vi.mocked(unassignSubject).mockReset();
|
||||
vi.mocked(fetchStaffing).mockResolvedValue(hiredTeacher());
|
||||
vi.mocked(fetchTimetable).mockResolvedValue(timetable());
|
||||
vi.mocked(fetchPerson).mockResolvedValue({
|
||||
...personCard(),
|
||||
id: 't1',
|
||||
fullName: 'Petrov Ivan',
|
||||
female: false,
|
||||
roles: ['staff'],
|
||||
position: 'Teacher',
|
||||
positionLabel: 'Teacher',
|
||||
});
|
||||
vi.mocked(fetchGameStatus).mockResolvedValue({
|
||||
tick: 0,
|
||||
tickRate: 20,
|
||||
schools: 1,
|
||||
maxSchools: 6,
|
||||
connections: 0,
|
||||
swarmUiConfigured: false,
|
||||
swarmUiConnected: null,
|
||||
});
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
document.body.replaceChildren();
|
||||
setLocale(initialLocale);
|
||||
});
|
||||
|
||||
it('shows allocated, committed and remaining', async () => {
|
||||
const panel = new ManagementPanel();
|
||||
document.body.append(panel.listElement, panel.cardElement);
|
||||
panel.show(2);
|
||||
await vi.waitFor(() => expect(panel.listElement.textContent).toContain(formatMoney(10_000)));
|
||||
expect(panel.listElement.textContent).toContain(t('staffAllocated'));
|
||||
expect(panel.listElement.textContent).toContain(t('staffPayroll'));
|
||||
expect(panel.listElement.textContent).toContain(t('staffRemaining'));
|
||||
expect(panel.listElement.textContent).toContain(formatMoney(4_000));
|
||||
expect(panel.listElement.textContent).toContain(formatMoney(6_000));
|
||||
});
|
||||
|
||||
it('assigns and unassigns a subject from the hired card', async () => {
|
||||
const assigned: Staffing = {
|
||||
...hiredTeacher(),
|
||||
payroll: 7_000,
|
||||
remaining: 3_000,
|
||||
staff: [
|
||||
{
|
||||
...hiredTeacher().staff[0],
|
||||
weeklyHours: 35,
|
||||
monthlyPay: 7_000,
|
||||
subjects: [{ defName: 'Mathematics', label: 'Math' }],
|
||||
},
|
||||
],
|
||||
};
|
||||
vi.mocked(assignSubject).mockResolvedValue(assigned);
|
||||
vi.mocked(unassignSubject).mockResolvedValue(hiredTeacher());
|
||||
|
||||
const panel = new ManagementPanel();
|
||||
document.body.append(panel.listElement, panel.cardElement);
|
||||
panel.show(2);
|
||||
const row = await vi.waitFor(() => {
|
||||
const node = panel.listElement.querySelector('tr.people__row');
|
||||
if (!(node instanceof HTMLTableRowElement)) {
|
||||
throw new Error('staff row is missing');
|
||||
}
|
||||
|
||||
return node;
|
||||
});
|
||||
|
||||
row.click();
|
||||
const assign = await vi.waitFor(() => {
|
||||
const button = [...panel.cardElement.querySelectorAll('button')].find(
|
||||
(candidate) => candidate.textContent === t('staffAssign'),
|
||||
);
|
||||
if (!(button instanceof HTMLButtonElement)) {
|
||||
throw new Error('assign button is missing');
|
||||
}
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
assign.click();
|
||||
await vi.waitFor(() => expect(assignSubject).toHaveBeenCalledWith(2, 't1', 'Mathematics', 'en'));
|
||||
await vi.waitFor(() => expect(panel.listElement.textContent).toContain(formatMoney(3_000)));
|
||||
|
||||
const unassign = await vi.waitFor(() => {
|
||||
const button = [...panel.cardElement.querySelectorAll('button')].find(
|
||||
(candidate) => candidate.textContent === t('staffUnassign'),
|
||||
);
|
||||
if (!(button instanceof HTMLButtonElement)) {
|
||||
throw new Error('unassign button is missing');
|
||||
}
|
||||
|
||||
return button;
|
||||
});
|
||||
|
||||
unassign.click();
|
||||
await vi.waitFor(() =>
|
||||
expect(unassignSubject).toHaveBeenCalledWith(2, 't1', 'Mathematics', 'en'),
|
||||
);
|
||||
await vi.waitFor(() => expect(panel.listElement.textContent).toContain(formatMoney(6_000)));
|
||||
});
|
||||
});
|
||||
|
||||
@@ -0,0 +1,122 @@
|
||||
/**
|
||||
* @vitest-environment happy-dom
|
||||
*/
|
||||
import { afterEach, describe, expect, it } from 'vitest';
|
||||
import type { CatalogResponse, MapLayout } from '../net/api.ts';
|
||||
import { getLocale, setLocale } from '../i18n/locale.ts';
|
||||
import { t } from '../i18n/strings.ts';
|
||||
import { mapEditor } from './mapEditor.ts';
|
||||
|
||||
const initialLocale = getLocale();
|
||||
|
||||
function catalog(): CatalogResponse {
|
||||
return {
|
||||
territories: [{ defName: 'Yard', label: 'Yard' }],
|
||||
buildings: [{ defName: 'SchoolBuilding', label: 'Building' }],
|
||||
floors: [{ defName: 'Floor', label: 'Floor' }],
|
||||
rooms: [
|
||||
{
|
||||
defName: 'Classroom',
|
||||
label: 'Classroom',
|
||||
homeroom: true,
|
||||
seatThing: 'StudentDesk',
|
||||
defaultSeats: 16,
|
||||
slots: [],
|
||||
positions: [],
|
||||
},
|
||||
{
|
||||
defName: 'Cafeteria',
|
||||
label: 'Cafeteria',
|
||||
homeroom: false,
|
||||
defaultSeats: 0,
|
||||
slots: [
|
||||
{ key: 'counter', thing: 'DiningTable', count: 1 },
|
||||
{ key: 'seats', thing: 'Chair', count: 8 },
|
||||
],
|
||||
positions: ['CafeteriaCook'],
|
||||
},
|
||||
],
|
||||
things: [
|
||||
{ defName: 'DiningTable', label: 'Table' },
|
||||
{ defName: 'Chair', label: 'Chair' },
|
||||
],
|
||||
defaultMap: map(),
|
||||
countries: [],
|
||||
subjects: [],
|
||||
dayFrame: null,
|
||||
holidays: [],
|
||||
};
|
||||
}
|
||||
|
||||
function map(): MapLayout {
|
||||
return {
|
||||
territory: { id: 'yard', def: 'Yard' },
|
||||
buildings: [{ id: 'b1', def: 'SchoolBuilding' }],
|
||||
floors: [{ id: 'f1', def: 'Floor', building: 'b1' }],
|
||||
rooms: [
|
||||
{ id: 'r-class', def: 'Classroom', building: 'b1', floor: 'f1', seats: 16 },
|
||||
{
|
||||
id: 'r-cafe',
|
||||
def: 'Cafeteria',
|
||||
building: 'b1',
|
||||
floor: 'f1',
|
||||
slots: [
|
||||
{ key: 'counter', thing: 'DiningTable', count: 1 },
|
||||
{ key: 'seats', thing: 'Chair', count: 8 },
|
||||
],
|
||||
},
|
||||
],
|
||||
links: [
|
||||
{ a: 'yard', b: 'b1' },
|
||||
{ a: 'b1', b: 'f1' },
|
||||
{ a: 'f1', b: 'r-class' },
|
||||
{ a: 'f1', b: 'r-cafe' },
|
||||
],
|
||||
};
|
||||
}
|
||||
|
||||
function furnishing(root: HTMLElement): HTMLElement {
|
||||
const section = [...root.querySelectorAll('.editor-section')].find((node) =>
|
||||
(node.querySelector('.editor-section__title')?.textContent ?? '').startsWith(t('editorSlots')),
|
||||
);
|
||||
if (!(section instanceof HTMLElement)) {
|
||||
throw new Error('furnishing section is missing');
|
||||
}
|
||||
|
||||
return section;
|
||||
}
|
||||
|
||||
function clickRoom(root: HTMLElement, name: string): void {
|
||||
const button = [...root.querySelectorAll('.tree__button')].find(
|
||||
(candidate) => candidate.textContent === name,
|
||||
);
|
||||
if (!(button instanceof HTMLButtonElement)) {
|
||||
throw new Error(`${name} is missing from the tree`);
|
||||
}
|
||||
|
||||
button.click();
|
||||
}
|
||||
|
||||
describe('mapEditor homeroom seats', () => {
|
||||
afterEach(() => {
|
||||
document.body.replaceChildren();
|
||||
setLocale(initialLocale);
|
||||
});
|
||||
|
||||
it('shows a seats field on a classroom and named slots on a cafeteria', () => {
|
||||
setLocale('en');
|
||||
const editor = mapEditor({ catalog: catalog(), map: map(), onChange: () => {} });
|
||||
document.body.append(editor.element);
|
||||
|
||||
clickRoom(editor.element, 'Classroom');
|
||||
const classroom = furnishing(editor.element);
|
||||
expect(classroom.textContent).toContain(t('editorSeats'));
|
||||
expect(classroom.querySelector('input.input--count')).not.toBeNull();
|
||||
expect(classroom.querySelector('select')).toBeNull();
|
||||
|
||||
clickRoom(editor.element, 'Cafeteria');
|
||||
const cafeteria = furnishing(editor.element);
|
||||
expect(cafeteria.textContent).not.toContain(t('editorSeats'));
|
||||
expect(cafeteria.querySelectorAll('select').length).toBeGreaterThan(0);
|
||||
});
|
||||
});
|
||||
Reference in New Issue
Block a user