Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
19 changes: 14 additions & 5 deletions src/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -1438,7 +1438,7 @@ interface NamedFormatter {
format: (result: Record<string, unknown>) => boolean;
}

const resultFormatters: NamedFormatter[] = [
export const resultFormatters: NamedFormatter[] = [
// Player status
{
name: 'player_status',
Expand Down Expand Up @@ -1684,10 +1684,16 @@ const resultFormatters: NamedFormatter[] = [
console.log(`(No wrecks at this location)`);
} else {
for (const w of wrecks) {
console.log(`\n${c.yellow}Wreck: ${w.wreck_id}${c.reset}`);
console.log(`\n${c.yellow}Wreck: ${w.id}${c.reset}`);
console.log(` Ship: ${w.ship_class}`);
console.log(` Expires in: ${w.ticks_remaining} ticks`);
const items = (w.items as Array<Record<string, unknown>>) || [];
// expire_tick === 0 means the wreck never expires (ship/pirate/abandoned wrecks);
// only jettisoned junk containers get a finite expiry.
if (w.expire_tick) {
console.log(` Expires at: ${w.expires_at ?? `tick ${w.expire_tick}`}`);
} else {
console.log(` Expires: never`);
}
const items = (w.cargo as Array<Record<string, unknown>>) || [];
if (items.length) {
console.log(` Contents:`);
for (const item of items) console.log(` - ${item.quantity}x ${item.item_id}`);
Expand Down Expand Up @@ -2644,4 +2650,7 @@ async function main(): Promise<void> {
}
}

main();
// Only run the CLI when executed directly, so the module can be imported by tests.
if (import.meta.main) {
main();
}
68 changes: 68 additions & 0 deletions src/wrecks-format.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,68 @@
/**
* Regression test for gh#1874 — get_wrecks output printed "undefined" for the
* wreck id and expiry because the formatter read fields the server never emits
* (`wreck_id`, `ticks_remaining`, `items`). The server emits `id`, `expire_tick`
* / `expires_at`, and `cargo`.
*/

import { describe, expect, test } from 'bun:test';
import { resultFormatters } from './client';

const wrecksFormatter = resultFormatters.find((f) => f.name === 'wrecks');
if (!wrecksFormatter) throw new Error('wrecks formatter not found');

function render(result: Record<string, unknown>): string {
const lines: string[] = [];
const orig = console.log;
console.log = (...args: unknown[]) => lines.push(args.join(' '));
try {
wrecksFormatter.format(result);
} finally {
console.log = orig;
}
// Colour codes wrap whole tokens, so the asserted substrings stay contiguous.
return lines.join('\n');
}

describe('get_wrecks formatter (gh#1874)', () => {
test('renders the real server fields, never the literal "undefined"', () => {
const out = render({
wrecks: [
{
id: 'wreck_abc123',
ship_class: 'eviction_notice',
expire_tick: 0, // ship/pirate/abandoned wrecks never expire
cargo: [{ item_id: 'titanium_ore', quantity: 12 }],
},
],
});

expect(out).not.toContain('undefined');
expect(out).toContain('Wreck: wreck_abc123');
expect(out).toContain('Ship: eviction_notice');
expect(out).toContain('Expires: never');
expect(out).toContain('12x titanium_ore');
});

test('shows a finite expiry for jettisoned containers that do expire', () => {
const out = render({
wrecks: [
{
id: 'wreck_junk',
ship_class: 'jettison',
expire_tick: 1500,
expires_at: '2026-07-23T00:00:00Z',
cargo: [],
},
],
});

expect(out).not.toContain('undefined');
expect(out).toContain('Expires at: 2026-07-23T00:00:00Z');
});

test('handles the empty-wrecks case', () => {
const out = render({ wrecks: [] });
expect(out).toContain('No wrecks at this location');
});
});
Loading