Merge main into fix/routes-panel-honesty

Both sides added a test beside the other in the dock's spec: the panel's own
departure guard here, the give-up wording on main. Both kept, and the offer test
carries the `least` the route answer now has.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_016jxMkwA2rbicdGxHosecYi
This commit is contained in:
2026-09-18 19:04:08 +02:00
co-authored by Claude Opus 5
17 changed files with 703 additions and 123 deletions
@@ -289,7 +289,7 @@ describe('HudDockComponent', () => {
// The offer beside a refusal is the same request by another route, so it is held to the same
// test: moving the range with nothing to plot leaves the panel contradicting itself.
fixture.componentRef.setInput('routeResult', { stars: [], totalPc: 0, neededRangePc: 1.8, gaveUp: false });
fixture.componentRef.setInput('routeResult', { stars: [], totalPc: 0, neededRangePc: 1.8, gaveUp: false, least: true });
fixture.detectChanges();
const offer = () => host().querySelector<HTMLButtonElement>('[data-testid="route-summary"] button')!;
expect(offer().disabled).toBe(false);
@@ -312,6 +312,33 @@ describe('HudDockComponent', () => {
expect(host().querySelector('#dock-panel-display')?.classList.contains('hud-acquire')).toBe(true);
});
it('says the search gave up rather than that there is no route, when that is what happened', () => {
fixture.componentRef.setInput('routing', true);
fixture.componentRef.setInput('defaultTab', 'routes');
const summary = () => host().querySelector('[data-testid="route-summary"]')?.textContent?.replace(/\s+/g, ' ').trim() ?? '';
fixture.componentRef.setInput('routeResult', { stars: [], totalPc: 0, neededRangePc: null, gaveUp: true, least: false });
fixture.detectChanges();
expect(summary()).toBe('Too many stars to search at this range.');
// Having looked everywhere the range reaches is a different answer, and one that can be stated.
fixture.componentRef.setInput('routeResult', { stars: [], totalPc: 0, neededRangePc: null, gaveUp: false, least: true });
fixture.detectChanges();
expect(summary()).toContain('No chain of jumps up to');
// A range a chain was found at is worth offering — but the search that gave up at the range
// asked for still gave up, and saying "no route" beside the offer is saying it did not.
fixture.componentRef.setInput('routeResult', { stars: [], totalPc: 0, neededRangePc: 6.4, gaveUp: true, least: false });
fixture.detectChanges();
expect(summary()).toBe('Too many stars to search at this range. 6.40 pc would reach.');
// The other way round: the range asked for was searched to exhaustion and the wider search was
// the one that gave up. There is no route at this range, and nothing further can be claimed.
fixture.componentRef.setInput('routeResult', { stars: [], totalPc: 0, neededRangePc: null, gaveUp: false, least: false });
fixture.detectChanges();
expect(summary()).toBe('No route at this range.');
});
it('keeps what the Routes panel was set to across a trip to another tab', () => {
setReadout();
fixture.componentRef.setInput('routing', true);
+11 -3
View File
@@ -20,8 +20,12 @@ export interface RouteResult {
/** The chain, departure first. Empty when there is no route at the range asked for. */
readonly stars: readonly { id: number; name: string }[];
readonly totalPc: number;
/** The shortest range that would open a route, where none was found at the one asked for. */
/** A range that would open a route, where none was found at the one asked for. */
readonly neededRangePc: number | null;
/** True when the search gave up rather than showing there is no route at this range. */
readonly gaveUp: boolean;
/** True when the search for a range that would work looked everywhere up to the widest offered. */
readonly least: boolean;
}
export interface RouteRequest {
@@ -113,7 +117,11 @@ type Field = 'from' | 'to';
</p>
} @else {
<p data-testid="route-summary" class="text-sm text-muted">
No route at this range.
@if (plotted.gaveUp) {
Too many stars to search at this range.
} @else {
No route at this range.
}
@if (plotted.neededRangePc !== null) {
<button
type="button"
@@ -123,7 +131,7 @@ type Field = 'from' | 'to';
>
{{ format(plotted.neededRangePc) }} would reach.
</button>
} @else {
} @else if (plotted.least) {
No chain of jumps up to {{ format(maxRangePc) }} reaches it.
}
</p>