@shipload/item-renderer 0.2.1 → 0.2.3

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (71) hide show
  1. package/.claude/settings.local.json +6 -0
  2. package/bun.lock +2 -2
  3. package/package.json +8 -4
  4. package/scripts/check-bundle-size.ts +21 -21
  5. package/scripts/copy-fonts.ts +19 -19
  6. package/scripts/preview.ts +13 -15
  7. package/src/assets/stardust-base64.ts +1 -1
  8. package/src/errors.ts +8 -8
  9. package/src/fonts/index.ts +25 -26
  10. package/src/fonts/load-bun.ts +9 -9
  11. package/src/index.ts +21 -21
  12. package/src/links.ts +11 -11
  13. package/src/meta.ts +16 -16
  14. package/src/payload/base64url.ts +16 -16
  15. package/src/payload/codec.ts +13 -13
  16. package/src/primitives/category-icon.ts +69 -48
  17. package/src/primitives/compact-row.ts +13 -13
  18. package/src/primitives/divider.ts +9 -9
  19. package/src/primitives/icon-hex.ts +18 -16
  20. package/src/primitives/module-slot.ts +73 -73
  21. package/src/primitives/panel.ts +10 -10
  22. package/src/primitives/quantity-badge.ts +13 -13
  23. package/src/primitives/span-paragraph.ts +48 -50
  24. package/src/primitives/stat-bar.ts +24 -24
  25. package/src/primitives/svg.ts +13 -13
  26. package/src/primitives/text.ts +25 -25
  27. package/src/primitives/wrap.ts +12 -12
  28. package/src/render.ts +15 -19
  29. package/src/templates/_shared.ts +6 -7
  30. package/src/templates/component.ts +68 -63
  31. package/src/templates/index.ts +17 -17
  32. package/src/templates/item-cell.ts +48 -41
  33. package/src/templates/module.ts +84 -83
  34. package/src/templates/packed-entity.ts +12 -14
  35. package/src/templates/resource.ts +63 -65
  36. package/src/templates/ship-panel.ts +67 -72
  37. package/src/templates/social-card.ts +27 -25
  38. package/src/tokens/colors.ts +29 -29
  39. package/src/tokens/index.ts +6 -6
  40. package/src/tokens/spacing.ts +1 -1
  41. package/src/tokens/typography.ts +1 -1
  42. package/test/__image_snapshots__/component-hull-plates.diff.png +0 -0
  43. package/test/__image_snapshots__/module-engine-t1.diff.png +0 -0
  44. package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
  45. package/test/__image_snapshots__/packed-entity-ship-t1-only-engine.diff.png +0 -0
  46. package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
  47. package/test/__image_snapshots__/resource-ore-t1.diff.png +0 -0
  48. package/test/base64url.test.ts +22 -22
  49. package/test/codec.test.ts +26 -35
  50. package/test/errors.test.ts +21 -21
  51. package/test/fixtures/cargo-items.ts +43 -43
  52. package/test/fonts.test.ts +23 -23
  53. package/test/links-meta.test.ts +37 -37
  54. package/test/pixel.test.ts +44 -41
  55. package/test/primitives-category-icon.test.ts +74 -67
  56. package/test/primitives-compact-row.test.ts +29 -29
  57. package/test/primitives-domain.test.ts +61 -50
  58. package/test/primitives-layout.test.ts +47 -47
  59. package/test/primitives-module-slot.test.ts +58 -58
  60. package/test/render.test.ts +38 -35
  61. package/test/sanity.test.ts +5 -5
  62. package/test/sdk-link.test.ts +13 -13
  63. package/test/svg.test.ts +24 -22
  64. package/test/templates-component.test.ts +32 -32
  65. package/test/templates-dispatch.test.ts +29 -29
  66. package/test/templates-item-cell.test.ts +79 -79
  67. package/test/templates-module.test.ts +52 -52
  68. package/test/templates-packed-entity.test.ts +42 -42
  69. package/test/templates-resource.test.ts +61 -61
  70. package/test/templates-ship-panel.test.ts +69 -65
  71. package/test/tokens.test.ts +28 -26
@@ -1,40 +1,43 @@
1
- import { expect, test } from 'bun:test'
2
- import { resolveItem } from '@shipload/sdk'
3
- import { renderItem, renderFromPayload } from '../src/render.ts'
4
- import { RenderError } from '../src/errors.ts'
5
- import { encodePayload } from '../src/payload/codec.ts'
6
- import { FIXTURES } from './fixtures/cargo-items.ts'
1
+ import { expect, test } from "bun:test";
2
+ import { resolveItem } from "@shipload/sdk";
3
+ import { renderItem, renderFromPayload } from "../src/render.ts";
4
+ import { RenderError } from "../src/errors.ts";
5
+ import { encodePayload } from "../src/payload/codec.ts";
6
+ import { FIXTURES } from "./fixtures/cargo-items.ts";
7
7
 
8
- test('renderItem dispatches to resource template for resources', () => {
9
- const item = FIXTURES.oreT1
10
- const resolved = resolveItem(item.item_id, item.stats, item.modules)
11
- const svg = renderItem(item, resolved)
12
- expect(svg).toContain('Crude Ore')
13
- })
8
+ test("renderItem dispatches to resource template for resources", () => {
9
+ const item = FIXTURES.oreT1;
10
+ const resolved = resolveItem(item.item_id, item.stats, item.modules);
11
+ const svg = renderItem(item, resolved);
12
+ expect(svg).toContain("Crude Ore");
13
+ });
14
14
 
15
- test('renderItem dispatches to packed entity template for entities', () => {
16
- const item = FIXTURES.shipT1TwoModules
17
- const resolved = resolveItem(item.item_id, item.stats, item.modules)
18
- const svg = renderItem(item, resolved)
19
- expect(svg).toContain('HULL')
20
- })
15
+ test("renderItem dispatches to packed entity template for entities", () => {
16
+ const item = FIXTURES.shipT1TwoModules;
17
+ const resolved = resolveItem(item.item_id, item.stats, item.modules);
18
+ const svg = renderItem(item, resolved);
19
+ expect(svg).toContain("HULL");
20
+ });
21
21
 
22
- test('renderItem dispatches to module template for modules', () => {
23
- const item = FIXTURES.engineT1
24
- const resolved = resolveItem(item.item_id, item.stats, item.modules)
25
- const svg = renderItem(item, resolved)
26
- expect(svg).toContain('MODULE')
27
- })
22
+ test("renderItem dispatches to module template for modules", () => {
23
+ const item = FIXTURES.engineT1;
24
+ const resolved = resolveItem(item.item_id, item.stats, item.modules);
25
+ const svg = renderItem(item, resolved);
26
+ expect(svg).toContain("MODULE");
27
+ });
28
28
 
29
- test('renderItem throws RenderError for unknown types', () => {
30
- const item = FIXTURES.oreT1
31
- const fake = { ...resolveItem(item.item_id, item.stats, item.modules), itemType: 'unknown' as never }
32
- expect(() => renderItem(item, fake)).toThrow(RenderError)
33
- })
29
+ test("renderItem throws RenderError for unknown types", () => {
30
+ const item = FIXTURES.oreT1;
31
+ const fake = {
32
+ ...resolveItem(item.item_id, item.stats, item.modules),
33
+ itemType: "unknown" as never,
34
+ };
35
+ expect(() => renderItem(item, fake)).toThrow(RenderError);
36
+ });
34
37
 
35
- test('renderFromPayload round-trips a payload into SVG', async () => {
36
- const payload = encodePayload(FIXTURES.oreT1)
37
- const { svg, item } = await renderFromPayload(payload)
38
- expect(svg).toContain('Crude Ore')
39
- expect(item.itemType).toBe('resource')
40
- })
38
+ test("renderFromPayload round-trips a payload into SVG", async () => {
39
+ const payload = encodePayload(FIXTURES.oreT1);
40
+ const { svg, item } = await renderFromPayload(payload);
41
+ expect(svg).toContain("Crude Ore");
42
+ expect(item.itemType).toBe("resource");
43
+ });
@@ -1,6 +1,6 @@
1
- import { expect, test } from 'bun:test'
2
- import { VERSION } from '../src/index.ts'
1
+ import { expect, test } from "bun:test";
2
+ import { VERSION } from "../src/index.ts";
3
3
 
4
- test('VERSION is a semver string', () => {
5
- expect(VERSION).toMatch(/^\d+\.\d+\.\d+$/)
6
- })
4
+ test("VERSION is a semver string", () => {
5
+ expect(VERSION).toMatch(/^\d+\.\d+\.\d+$/);
6
+ });
@@ -1,19 +1,19 @@
1
- import { expect, test } from 'bun:test'
2
- import { resolveItem, ServerContract, type ResolvedItem } from '@shipload/sdk'
1
+ import { expect, test } from "bun:test";
2
+ import { resolveItem, ServerContract, type ResolvedItem } from "@shipload/sdk";
3
3
 
4
- test('sdkv2 is linked and exposes resolveItem + ResolvedItem type', () => {
5
- const resolved: ResolvedItem = resolveItem(101 /* T1 Ore */, undefined, undefined)
6
- expect(resolved.itemId).toBe(101)
7
- expect(resolved.category).toBe('ore')
8
- expect(resolved.itemType).toBe('resource')
9
- })
4
+ test("sdkv2 is linked and exposes resolveItem + ResolvedItem type", () => {
5
+ const resolved: ResolvedItem = resolveItem(101 /* T1 Ore */, undefined, undefined);
6
+ expect(resolved.itemId).toBe(101);
7
+ expect(resolved.category).toBe("ore");
8
+ expect(resolved.itemType).toBe("resource");
9
+ });
10
10
 
11
- test('ServerContract.Types.cargo_item can be constructed', () => {
11
+ test("ServerContract.Types.cargo_item can be constructed", () => {
12
12
  const ci = ServerContract.Types.cargo_item.from({
13
13
  item_id: 101,
14
14
  quantity: 1,
15
- stats: '0',
15
+ stats: "0",
16
16
  modules: [],
17
- })
18
- expect(ci.item_id.equals(101)).toBe(true)
19
- })
17
+ });
18
+ expect(ci.item_id.equals(101)).toBe(true);
19
+ });
package/test/svg.test.ts CHANGED
@@ -1,28 +1,30 @@
1
- import { expect, test } from 'bun:test'
2
- import { attr, el, escapeXml } from '../src/primitives/svg.ts'
1
+ import { expect, test } from "bun:test";
2
+ import { attr, el, escapeXml } from "../src/primitives/svg.ts";
3
3
 
4
- test('escapeXml escapes the five XML entity chars', () => {
4
+ test("escapeXml escapes the five XML entity chars", () => {
5
5
  expect(escapeXml(`5 > 3 & 2 < 4 "hi" 'yo'`)).toBe(
6
- '5 &gt; 3 &amp; 2 &lt; 4 &quot;hi&quot; &apos;yo&apos;',
7
- )
8
- })
6
+ "5 &gt; 3 &amp; 2 &lt; 4 &quot;hi&quot; &apos;yo&apos;",
7
+ );
8
+ });
9
9
 
10
- test('attr emits name=value pairs with quoted values', () => {
11
- expect(attr({ width: 10, height: 20, fill: '#123456' }))
12
- .toBe(' width="10" height="20" fill="#123456"')
13
- })
10
+ test("attr emits name=value pairs with quoted values", () => {
11
+ expect(attr({ width: 10, height: 20, fill: "#123456" })).toBe(
12
+ ' width="10" height="20" fill="#123456"',
13
+ );
14
+ });
14
15
 
15
- test('attr drops undefined and null values', () => {
16
- expect(attr({ width: 10, height: undefined, stroke: null as unknown as string }))
17
- .toBe(' width="10"')
18
- })
16
+ test("attr drops undefined and null values", () => {
17
+ expect(attr({ width: 10, height: undefined, stroke: null as unknown as string })).toBe(
18
+ ' width="10"',
19
+ );
20
+ });
19
21
 
20
- test('attr escapes special chars in values', () => {
21
- expect(attr({ label: `ship "fast"` })).toBe(' label="ship &quot;fast&quot;"')
22
- })
22
+ test("attr escapes special chars in values", () => {
23
+ expect(attr({ label: `ship "fast"` })).toBe(' label="ship &quot;fast&quot;"');
24
+ });
23
25
 
24
- test('el builds self-closing and wrapping elements', () => {
25
- expect(el('rect', { width: 10, height: 10 })).toBe('<rect width="10" height="10"/>')
26
- expect(el('g', {}, '<rect/><rect/>')).toBe('<g><rect/><rect/></g>')
27
- expect(el('text', { x: 5 }, 'hi')).toBe('<text x="5">hi</text>')
28
- })
26
+ test("el builds self-closing and wrapping elements", () => {
27
+ expect(el("rect", { width: 10, height: 10 })).toBe('<rect width="10" height="10"/>');
28
+ expect(el("g", {}, "<rect/><rect/>")).toBe("<g><rect/><rect/></g>");
29
+ expect(el("text", { x: 5 }, "hi")).toBe('<text x="5">hi</text>');
30
+ });
@@ -1,36 +1,36 @@
1
- import { expect, test } from 'bun:test'
2
- import { resolveItem } from '@shipload/sdk'
3
- import { renderComponent } from '../src/templates/component.ts'
4
- import { FIXTURES } from './fixtures/cargo-items.ts'
1
+ import { expect, test } from "bun:test";
2
+ import { resolveItem } from "@shipload/sdk";
3
+ import { renderComponent } from "../src/templates/component.ts";
4
+ import { FIXTURES } from "./fixtures/cargo-items.ts";
5
5
 
6
- test('matches the committed Hull Plates snapshot', async () => {
7
- const item = FIXTURES.hullPlates
8
- const resolved = resolveItem(item.item_id, item.stats, item.modules)
9
- const svg = renderComponent(item, resolved)
10
- expect(svg).toMatchSnapshot('component-hull-plates.svg')
11
- })
6
+ test("matches the committed Hull Plates snapshot", async () => {
7
+ const item = FIXTURES.hullPlates;
8
+ const resolved = resolveItem(item.item_id, item.stats, item.modules);
9
+ const svg = renderComponent(item, resolved);
10
+ expect(svg).toMatchSnapshot("component-hull-plates.svg");
11
+ });
12
12
 
13
- test('renderComponent ranges mode shows stat abbreviations with no values', () => {
14
- const item = FIXTURES.hullPlates
15
- const resolved = resolveItem(item.item_id)
16
- const svg = renderComponent(item, resolved, { mode: 'ranges' })
17
- expect(svg).toContain('STR')
18
- expect(svg).toContain('DEN')
19
- expect(svg).not.toMatch(/>\d{3}<\/text>/)
20
- expect(svg).toContain('COMPONENT')
21
- expect(svg).toContain('Mass')
22
- })
13
+ test("renderComponent ranges mode shows stat abbreviations with no values", () => {
14
+ const item = FIXTURES.hullPlates;
15
+ const resolved = resolveItem(item.item_id);
16
+ const svg = renderComponent(item, resolved, { mode: "ranges" });
17
+ expect(svg).toContain("STR");
18
+ expect(svg).toContain("DEN");
19
+ expect(svg).not.toMatch(/>\d{3}<\/text>/);
20
+ expect(svg).toContain("COMPONENT");
21
+ expect(svg).toContain("Mass");
22
+ });
23
23
 
24
- test('renderComponent values mode (default) still shows concrete numbers', () => {
25
- const item = FIXTURES.hullPlates
26
- const resolved = resolveItem(item.item_id, item.stats)
27
- const svg = renderComponent(item, resolved)
28
- expect(svg).toMatch(/>\d+<\/text>/)
29
- })
24
+ test("renderComponent values mode (default) still shows concrete numbers", () => {
25
+ const item = FIXTURES.hullPlates;
26
+ const resolved = resolveItem(item.item_id, item.stats);
27
+ const svg = renderComponent(item, resolved);
28
+ expect(svg).toMatch(/>\d+<\/text>/);
29
+ });
30
30
 
31
- test('renderComponent ranges mode matches snapshot', () => {
32
- const item = FIXTURES.hullPlates
33
- const resolved = resolveItem(item.item_id)
34
- const svg = renderComponent(item, resolved, { mode: 'ranges' })
35
- expect(svg).toMatchSnapshot('component-ranges')
36
- })
31
+ test("renderComponent ranges mode matches snapshot", () => {
32
+ const item = FIXTURES.hullPlates;
33
+ const resolved = resolveItem(item.item_id);
34
+ const svg = renderComponent(item, resolved, { mode: "ranges" });
35
+ expect(svg).toMatchSnapshot("component-ranges");
36
+ });
@@ -1,35 +1,35 @@
1
- import { test, expect } from 'bun:test'
2
- import { resolveItem } from '@shipload/sdk'
3
- import { renderByType } from '../src/templates/index.ts'
4
- import { FIXTURES } from './fixtures/cargo-items.ts'
1
+ import { test, expect } from "bun:test";
2
+ import { resolveItem } from "@shipload/sdk";
3
+ import { renderByType } from "../src/templates/index.ts";
4
+ import { FIXTURES } from "./fixtures/cargo-items.ts";
5
5
 
6
- test('renderByType forwards mode=ranges to resource template', () => {
7
- const item = FIXTURES.oreT1
8
- const resolved = resolveItem(item.item_id)
9
- const svg = renderByType(item, resolved, { mode: 'ranges' })
6
+ test("renderByType forwards mode=ranges to resource template", () => {
7
+ const item = FIXTURES.oreT1;
8
+ const resolved = resolveItem(item.item_id);
9
+ const svg = renderByType(item, resolved, { mode: "ranges" });
10
10
  // No 3-digit stat values
11
- expect(svg).not.toMatch(/>\d{3,}<\/text>/)
12
- })
11
+ expect(svg).not.toMatch(/>\d{3,}<\/text>/);
12
+ });
13
13
 
14
- test('renderByType forwards mode=ranges to component template', () => {
15
- const item = FIXTURES.hullPlates
16
- const resolved = resolveItem(item.item_id)
17
- const svg = renderByType(item, resolved, { mode: 'ranges' })
18
- expect(svg).not.toMatch(/>\d{3,}<\/text>/)
19
- expect(svg).toContain('COMPONENT')
20
- })
14
+ test("renderByType forwards mode=ranges to component template", () => {
15
+ const item = FIXTURES.hullPlates;
16
+ const resolved = resolveItem(item.item_id);
17
+ const svg = renderByType(item, resolved, { mode: "ranges" });
18
+ expect(svg).not.toMatch(/>\d{3,}<\/text>/);
19
+ expect(svg).toContain("COMPONENT");
20
+ });
21
21
 
22
- test('renderByType forwards mode=ranges to module template', () => {
23
- const item = FIXTURES.engineT1
24
- const resolved = resolveItem(item.item_id)
25
- const svg = renderByType(item, resolved, { mode: 'ranges' })
26
- expect(svg).toContain('MODULE')
27
- })
22
+ test("renderByType forwards mode=ranges to module template", () => {
23
+ const item = FIXTURES.engineT1;
24
+ const resolved = resolveItem(item.item_id);
25
+ const svg = renderByType(item, resolved, { mode: "ranges" });
26
+ expect(svg).toContain("MODULE");
27
+ });
28
28
 
29
- test('renderByType default mode is values (no opts)', () => {
30
- const item = FIXTURES.oreT1
31
- const resolved = resolveItem(item.item_id, item.stats)
32
- const svg = renderByType(item, resolved)
29
+ test("renderByType default mode is values (no opts)", () => {
30
+ const item = FIXTURES.oreT1;
31
+ const resolved = resolveItem(item.item_id, item.stats);
32
+ const svg = renderByType(item, resolved);
33
33
  // At least one numeric stat value
34
- expect(svg).toMatch(/>\d+<\/text>/)
35
- })
34
+ expect(svg).toMatch(/>\d+<\/text>/);
35
+ });
@@ -1,94 +1,94 @@
1
- import { test, expect } from 'bun:test'
2
- import { resolveItem } from '@shipload/sdk'
3
- import { ITEM_HULL_PLATES, ITEM_ENGINE_T1, ITEM_SHIP_T1_PACKED } from '@shipload/sdk'
4
- import { renderItemCell, itemCellGroup } from '../src/templates/item-cell.ts'
1
+ import { test, expect } from "bun:test";
2
+ import { resolveItem } from "@shipload/sdk";
3
+ import { ITEM_HULL_PLATES, ITEM_ENGINE_T1, ITEM_SHIP_T1_PACKED } from "@shipload/sdk";
4
+ import { renderItemCell, itemCellGroup } from "../src/templates/item-cell.ts";
5
5
 
6
- test('renderItemCell returns a self-contained <svg>', () => {
7
- const resolved = resolveItem(ITEM_HULL_PLATES)
8
- const svg = renderItemCell({ resolved, size: 48 })
9
- expect(svg.startsWith('<svg ')).toBe(true)
10
- expect(svg).toContain('viewBox="0 0 48 60"')
11
- expect(svg.endsWith('</svg>')).toBe(true)
12
- })
6
+ test("renderItemCell returns a self-contained <svg>", () => {
7
+ const resolved = resolveItem(ITEM_HULL_PLATES);
8
+ const svg = renderItemCell({ resolved, size: 48 });
9
+ expect(svg.startsWith("<svg ")).toBe(true);
10
+ expect(svg).toContain('viewBox="0 0 48 60"');
11
+ expect(svg.endsWith("</svg>")).toBe(true);
12
+ });
13
13
 
14
- test('component cell renders abbreviation', () => {
15
- const resolved = resolveItem(ITEM_HULL_PLATES)
16
- const svg = renderItemCell({ resolved, size: 48 })
17
- expect(svg).toContain('>HP<')
18
- })
14
+ test("component cell renders abbreviation", () => {
15
+ const resolved = resolveItem(ITEM_HULL_PLATES);
16
+ const svg = renderItemCell({ resolved, size: 48 });
17
+ expect(svg).toContain(">HP<");
18
+ });
19
19
 
20
- test('module cell renders abbreviation', () => {
21
- const resolved = resolveItem(ITEM_ENGINE_T1)
22
- const svg = renderItemCell({ resolved, size: 48 })
23
- expect(svg).toContain('>EN<')
24
- })
20
+ test("module cell renders abbreviation", () => {
21
+ const resolved = resolveItem(ITEM_ENGINE_T1);
22
+ const svg = renderItemCell({ resolved, size: 48 });
23
+ expect(svg).toContain(">EN<");
24
+ });
25
25
 
26
- test('entity cell renders abbreviation', () => {
27
- const resolved = resolveItem(ITEM_SHIP_T1_PACKED)
28
- const svg = renderItemCell({ resolved, size: 48 })
29
- expect(svg).toContain('>SH<')
30
- })
26
+ test("entity cell renders abbreviation", () => {
27
+ const resolved = resolveItem(ITEM_SHIP_T1_PACKED);
28
+ const svg = renderItemCell({ resolved, size: 48 });
29
+ expect(svg).toContain(">SH<");
30
+ });
31
31
 
32
- test('resource cell renders category icon (no abbreviation)', () => {
33
- const resolved = resolveItem(101)
34
- const svg = renderItemCell({ resolved, size: 48 })
35
- expect(svg).not.toMatch(/>[A-Z]{2,3}</)
36
- expect(svg).toMatch(/<(polygon|circle|rect)\b/)
37
- })
32
+ test("resource cell renders category icon (no abbreviation)", () => {
33
+ const resolved = resolveItem(101);
34
+ const svg = renderItemCell({ resolved, size: 48 });
35
+ expect(svg).not.toMatch(/>[A-Z]{2,3}</);
36
+ expect(svg).toMatch(/<(polygon|circle|rect)\b/);
37
+ });
38
38
 
39
- test('quantity renders as plain bold number when quantity > 1', () => {
40
- const resolved = resolveItem(ITEM_HULL_PLATES)
41
- const svg = renderItemCell({ resolved, quantity: 42, size: 48 })
42
- expect(svg).toContain('>42<')
43
- expect(svg).not.toContain('×')
44
- })
39
+ test("quantity renders as plain bold number when quantity > 1", () => {
40
+ const resolved = resolveItem(ITEM_HULL_PLATES);
41
+ const svg = renderItemCell({ resolved, quantity: 42, size: 48 });
42
+ expect(svg).toContain(">42<");
43
+ expect(svg).not.toContain("×");
44
+ });
45
45
 
46
- test('no quantity text when quantity is 1 or omitted', () => {
47
- const resolved = resolveItem(ITEM_HULL_PLATES)
48
- const svgNoQty = renderItemCell({ resolved, size: 48 })
49
- const svgOne = renderItemCell({ resolved, quantity: 1, size: 48 })
50
- expect(svgNoQty).not.toContain('>1<')
51
- expect(svgOne).not.toContain('>1<')
52
- })
46
+ test("no quantity text when quantity is 1 or omitted", () => {
47
+ const resolved = resolveItem(ITEM_HULL_PLATES);
48
+ const svgNoQty = renderItemCell({ resolved, size: 48 });
49
+ const svgOne = renderItemCell({ resolved, quantity: 1, size: 48 });
50
+ expect(svgNoQty).not.toContain(">1<");
51
+ expect(svgOne).not.toContain(">1<");
52
+ });
53
53
 
54
- test('itemCellGroup returns <g> with translate, no <svg> wrapper', () => {
55
- const resolved = resolveItem(ITEM_HULL_PLATES)
56
- const g = itemCellGroup({ resolved, size: 48, x: 100, y: 200 })
57
- expect(g.startsWith('<g ')).toBe(true)
58
- expect(g).toContain('transform="translate(100, 200)"')
59
- expect(g.startsWith('<svg')).toBe(false)
60
- })
54
+ test("itemCellGroup returns <g> with translate, no <svg> wrapper", () => {
55
+ const resolved = resolveItem(ITEM_HULL_PLATES);
56
+ const g = itemCellGroup({ resolved, size: 48, x: 100, y: 200 });
57
+ expect(g.startsWith("<g ")).toBe(true);
58
+ expect(g).toContain('transform="translate(100, 200)"');
59
+ expect(g.startsWith("<svg")).toBe(false);
60
+ });
61
61
 
62
- test('tier border uses SDK tierColors for the resolved tier', () => {
63
- const resolved = resolveItem(ITEM_HULL_PLATES)
64
- const svg = renderItemCell({ resolved, size: 48 })
65
- expect(svg).toContain('#8b8b8b')
66
- })
62
+ test("tier border uses SDK tierColors for the resolved tier", () => {
63
+ const resolved = resolveItem(ITEM_HULL_PLATES);
64
+ const svg = renderItemCell({ resolved, size: 48 });
65
+ expect(svg).toContain("#8b8b8b");
66
+ });
67
67
 
68
- test('abbreviation cell uses proportional font size for different sizes', () => {
69
- const resolved = resolveItem(ITEM_HULL_PLATES)
70
- const svg28 = renderItemCell({ resolved, size: 28 })
71
- const svg80 = renderItemCell({ resolved, size: 80 })
72
- expect(svg28).toContain('font-size="8"')
73
- expect(svg80).toContain('font-size="22"')
74
- })
68
+ test("abbreviation cell uses proportional font size for different sizes", () => {
69
+ const resolved = resolveItem(ITEM_HULL_PLATES);
70
+ const svg28 = renderItemCell({ resolved, size: 28 });
71
+ const svg80 = renderItemCell({ resolved, size: 80 });
72
+ expect(svg28).toContain('font-size="8"');
73
+ expect(svg80).toContain('font-size="22"');
74
+ });
75
75
 
76
- test('resource icon renders in stroke-only mode', () => {
77
- const resolved = resolveItem(101)
78
- const svg = renderItemCell({ resolved, size: 48 })
79
- expect(svg).toContain('fill="none"')
80
- expect(svg).toContain('stroke-width="1.5"')
81
- })
76
+ test("resource icon renders in stroke-only mode", () => {
77
+ const resolved = resolveItem(101);
78
+ const svg = renderItemCell({ resolved, size: 48 });
79
+ expect(svg).toContain('fill="none"');
80
+ expect(svg).toContain('stroke-width="1.5"');
81
+ });
82
82
 
83
- test('matches golden SVG snapshot per itemType', () => {
83
+ test("matches golden SVG snapshot per itemType", () => {
84
84
  const cases: [number, string][] = [
85
- [101, 'item-cell-resource'],
86
- [ITEM_HULL_PLATES, 'item-cell-component'],
87
- [ITEM_ENGINE_T1, 'item-cell-module'],
88
- [ITEM_SHIP_T1_PACKED, 'item-cell-entity'],
89
- ]
85
+ [101, "item-cell-resource"],
86
+ [ITEM_HULL_PLATES, "item-cell-component"],
87
+ [ITEM_ENGINE_T1, "item-cell-module"],
88
+ [ITEM_SHIP_T1_PACKED, "item-cell-entity"],
89
+ ];
90
90
  for (const [id, name] of cases) {
91
- const svg = renderItemCell({ resolved: resolveItem(id), quantity: 3, size: 48 })
92
- expect(svg).toMatchSnapshot(name)
91
+ const svg = renderItemCell({ resolved: resolveItem(id), quantity: 3, size: 48 });
92
+ expect(svg).toMatchSnapshot(name);
93
93
  }
94
- })
94
+ });
@@ -1,63 +1,63 @@
1
- import { test, expect } from 'bun:test'
2
- import { resolveItem } from '@shipload/sdk'
3
- import { renderModule } from '../src/templates/module.ts'
4
- import { FIXTURES } from './fixtures/cargo-items.ts'
1
+ import { test, expect } from "bun:test";
2
+ import { resolveItem } from "@shipload/sdk";
3
+ import { renderModule } from "../src/templates/module.ts";
4
+ import { FIXTURES } from "./fixtures/cargo-items.ts";
5
5
 
6
6
  const CASES = [
7
- 'engineT1',
8
- 'generatorT1',
9
- 'gathererT1',
10
- 'loaderT1',
11
- 'crafterT1',
12
- 'storageT1',
13
- 'haulerT1',
14
- ] as const
7
+ "engineT1",
8
+ "generatorT1",
9
+ "gathererT1",
10
+ "loaderT1",
11
+ "crafterT1",
12
+ "storageT1",
13
+ "haulerT1",
14
+ ] as const;
15
15
 
16
16
  for (const name of CASES) {
17
17
  test(`matches the committed ${name} snapshot`, async () => {
18
- const item = FIXTURES[name]
19
- const resolved = resolveItem(item.item_id, item.stats, item.modules)
20
- const svg = renderModule(item, resolved)
21
- expect(svg).toMatchSnapshot(`module-${name}.svg`)
22
- })
18
+ const item = FIXTURES[name];
19
+ const resolved = resolveItem(item.item_id, item.stats, item.modules);
20
+ const svg = renderModule(item, resolved);
21
+ expect(svg).toMatchSnapshot(`module-${name}.svg`);
22
+ });
23
23
  }
24
24
 
25
- test('Engine template embeds the narrative description', () => {
26
- const item = FIXTURES.engineT1
27
- const resolved = resolveItem(item.item_id, item.stats, item.modules)
28
- const svg = renderModule(item, resolved)
29
- expect(svg).toContain('generates')
30
- expect(svg).toContain('thrust for travel')
31
- expect(svg).toContain('while draining')
32
- expect(svg).toContain('distance travelled')
33
- })
25
+ test("Engine template embeds the narrative description", () => {
26
+ const item = FIXTURES.engineT1;
27
+ const resolved = resolveItem(item.item_id, item.stats, item.modules);
28
+ const svg = renderModule(item, resolved);
29
+ expect(svg).toContain("generates");
30
+ expect(svg).toContain("thrust for travel");
31
+ expect(svg).toContain("while draining");
32
+ expect(svg).toContain("distance travelled");
33
+ });
34
34
 
35
- test('Hauler template falls back to compact rows when description is null', () => {
36
- const item = FIXTURES.haulerT1
37
- const resolved = resolveItem(item.item_id, item.stats, item.modules)
38
- const svg = renderModule(item, resolved)
39
- expect(svg).toContain('Hauler')
40
- })
35
+ test("Hauler template falls back to compact rows when description is null", () => {
36
+ const item = FIXTURES.haulerT1;
37
+ const resolved = resolveItem(item.item_id, item.stats, item.modules);
38
+ const svg = renderModule(item, resolved);
39
+ expect(svg).toContain("Hauler");
40
+ });
41
41
 
42
- test('renderModule ranges mode shows capability header without narrative or attribute values', () => {
43
- const item = FIXTURES.engineT1
44
- const resolved = resolveItem(item.item_id)
45
- const svg = renderModule(item, resolved, { mode: 'ranges' })
46
- expect(svg).toContain('ENGINE')
47
- expect(svg).not.toMatch(/>\d{3,}<\/(text|tspan)>/)
48
- expect(svg).toContain('MODULE')
49
- })
42
+ test("renderModule ranges mode shows capability header without narrative or attribute values", () => {
43
+ const item = FIXTURES.engineT1;
44
+ const resolved = resolveItem(item.item_id);
45
+ const svg = renderModule(item, resolved, { mode: "ranges" });
46
+ expect(svg).toContain("ENGINE");
47
+ expect(svg).not.toMatch(/>\d{3,}<\/(text|tspan)>/);
48
+ expect(svg).toContain("MODULE");
49
+ });
50
50
 
51
- test('renderModule values mode (default) still shows narrative', () => {
52
- const item = FIXTURES.engineT1
53
- const resolved = resolveItem(item.item_id, item.stats)
54
- const svg = renderModule(item, resolved)
55
- expect(svg).toMatch(/thrust|energy|generates/)
56
- })
51
+ test("renderModule values mode (default) still shows narrative", () => {
52
+ const item = FIXTURES.engineT1;
53
+ const resolved = resolveItem(item.item_id, item.stats);
54
+ const svg = renderModule(item, resolved);
55
+ expect(svg).toMatch(/thrust|energy|generates/);
56
+ });
57
57
 
58
- test('renderModule ranges mode matches snapshot', () => {
59
- const item = FIXTURES.engineT1
60
- const resolved = resolveItem(item.item_id)
61
- const svg = renderModule(item, resolved, { mode: 'ranges' })
62
- expect(svg).toMatchSnapshot('module-ranges')
63
- })
58
+ test("renderModule ranges mode matches snapshot", () => {
59
+ const item = FIXTURES.engineT1;
60
+ const resolved = resolveItem(item.item_id);
61
+ const svg = renderModule(item, resolved, { mode: "ranges" });
62
+ expect(svg).toMatchSnapshot("module-ranges");
63
+ });