Files
Dune/tests/standalone-shipment-sector-controls.test.tsx

57 lines
3.8 KiB
TypeScript

import assert from 'node:assert/strict';
import test from 'node:test';
import { createElement, isValidElement, type ReactElement, type ReactNode } from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { ShipmentClauseFields, invalidShipmentClause, type ShipmentClauseInput } from '../components/shipment-claim-fields';
import { shipmentExpressionInputError } from '../game/shipment-expression-input';
import type { ShipmentExpression } from '../game/shipment-promises';
type Element = ReactElement<{ children?: ReactNode; [key: string]: unknown }>;
function elements(node: ReactNode): Element[] {
if (Array.isArray(node)) return node.flatMap(elements);
if (!isValidElement<{ children?: ReactNode; [key: string]: unknown }>(node)) return [];
return [node, ...elements(node.props.children)];
}
void test('standalone shipment editor defaults to any sector and accepts only printed sectors', () => {
let value: ShipmentClauseInput = { territory: 'wind_pass', minimum: 2 };
const tree = () => ShipmentClauseFields({ id: 'shipment', value, onChange(next) { value = next; } });
const select = (suffix: string, chosen: string) => {
const element = elements(tree()).find(item => item.props.id === `shipment-${suffix}`); assert.ok(element);
(element.props.onChange as (event: { currentTarget: { value: string }; target: { value: string } }) => void)(
{ currentTarget: { value: chosen }, target: { value: chosen } });
};
const html = renderToStaticMarkup(createElement(ShipmentClauseFields, { id: 'shipment', value, onChange() {} }));
assert.match(html, /Any sector in this territory/);
assert.match(html, /Sector 14/);
assert.doesNotMatch(html, /Sector 9/);
select('sector', '15'); assert.deepEqual(value, { territory: 'wind_pass', minimum: 2, sector: 15 });
assert.equal(invalidShipmentClause(value), false);
select('sector', '19'); assert.deepEqual(value, { territory: 'wind_pass', minimum: 2, sector: 15 });
select('sector', ''); assert.deepEqual(value, { territory: 'wind_pass', minimum: 2 });
select('sector', '14'); select('territory', 'arrakeen');
assert.deepEqual(value, { territory: 'arrakeen', minimum: 2 });
});
void test('sector input validation checks the actual territory while preserving incomplete legacy drafts', () => {
const exact: ShipmentExpression = { territory: 'wind_pass', minimum: 2, sector: 15 };
assert.equal(shipmentExpressionInputError(exact), null);
assert.notEqual(shipmentExpressionInputError({ ...exact, sector: 9 }), null);
assert.equal(invalidShipmentClause({ territory: 'wind_pass', minimum: 2, sector: 9 }), true);
assert.equal(invalidShipmentClause({ territory: 'wind_pass', minimum: 0 }), true);
// Structural input checks intentionally allow ordinary fields to be repaired.
assert.equal(shipmentExpressionInputError({ territory: '', minimum: 0 }), null);
assert.equal(shipmentExpressionInputError({ op: 'or', terms: [exact, { territory: 'carthag', minimum: 1 }] }), null);
assert.notEqual(shipmentExpressionInputError({ op: 'and', terms: [exact,
{ territory: 'wind_pass', minimum: 2, sector: 3 }] }), null);
});
void test('mixed-shipment fields remain on their existing territory-only question interface', () => {
const mixed = { kind: 'shipment' as const, territory: 'wind_pass', minimum: 2 };
let result: ShipmentClauseInput = mixed;
const tree = () => ShipmentClauseFields({ id: 'mixed', value: mixed, onChange(next) { result = next; } });
assert.equal(elements(tree()).some(item => item.props.id === 'mixed-sector'), false);
const territory = elements(tree()).find(item => item.props.id === 'mixed-territory'); assert.ok(territory);
(territory.props.onChange as (event: { target: { value: string } }) => void)({ target: { value: 'arrakeen' } });
assert.deepEqual(result, { kind: 'shipment', territory: 'arrakeen', minimum: 2 });
});