@shipload/item-renderer 0.2.2 → 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.
- package/.claude/settings.local.json +6 -0
- package/bun.lock +2 -2
- package/package.json +2 -2
- package/scripts/check-bundle-size.ts +21 -21
- package/scripts/copy-fonts.ts +19 -19
- package/scripts/preview.ts +13 -15
- package/src/assets/stardust-base64.ts +1 -1
- package/src/errors.ts +8 -8
- package/src/fonts/index.ts +25 -26
- package/src/fonts/load-bun.ts +9 -9
- package/src/index.ts +21 -21
- package/src/links.ts +11 -11
- package/src/meta.ts +16 -16
- package/src/payload/base64url.ts +16 -16
- package/src/payload/codec.ts +13 -13
- package/src/primitives/category-icon.ts +69 -48
- package/src/primitives/compact-row.ts +13 -13
- package/src/primitives/divider.ts +9 -9
- package/src/primitives/icon-hex.ts +18 -16
- package/src/primitives/module-slot.ts +73 -73
- package/src/primitives/panel.ts +10 -10
- package/src/primitives/quantity-badge.ts +13 -13
- package/src/primitives/span-paragraph.ts +48 -50
- package/src/primitives/stat-bar.ts +24 -24
- package/src/primitives/svg.ts +13 -13
- package/src/primitives/text.ts +25 -25
- package/src/primitives/wrap.ts +12 -12
- package/src/render.ts +15 -19
- package/src/templates/_shared.ts +5 -6
- package/src/templates/component.ts +67 -65
- package/src/templates/index.ts +17 -17
- package/src/templates/item-cell.ts +48 -45
- package/src/templates/module.ts +83 -81
- package/src/templates/packed-entity.ts +12 -14
- package/src/templates/resource.ts +63 -65
- package/src/templates/ship-panel.ts +66 -71
- package/src/templates/social-card.ts +27 -25
- package/src/tokens/colors.ts +29 -29
- package/src/tokens/index.ts +6 -6
- package/src/tokens/spacing.ts +1 -1
- package/src/tokens/typography.ts +1 -1
- package/test/__image_snapshots__/module-storage-t1.diff.png +0 -0
- package/test/__image_snapshots__/packed-entity-ship-t1-two-modules.diff.png +0 -0
- package/test/base64url.test.ts +22 -22
- package/test/codec.test.ts +26 -35
- package/test/errors.test.ts +21 -21
- package/test/fixtures/cargo-items.ts +43 -43
- package/test/fonts.test.ts +23 -23
- package/test/links-meta.test.ts +37 -37
- package/test/pixel.test.ts +44 -41
- package/test/primitives-category-icon.test.ts +74 -67
- package/test/primitives-compact-row.test.ts +29 -29
- package/test/primitives-domain.test.ts +61 -50
- package/test/primitives-layout.test.ts +47 -47
- package/test/primitives-module-slot.test.ts +58 -58
- package/test/render.test.ts +38 -35
- package/test/sanity.test.ts +5 -5
- package/test/sdk-link.test.ts +13 -13
- package/test/svg.test.ts +24 -22
- package/test/templates-component.test.ts +32 -32
- package/test/templates-dispatch.test.ts +29 -29
- package/test/templates-item-cell.test.ts +79 -79
- package/test/templates-module.test.ts +52 -52
- package/test/templates-packed-entity.test.ts +42 -42
- package/test/templates-resource.test.ts +61 -61
- package/test/templates-ship-panel.test.ts +65 -61
- package/test/tokens.test.ts +28 -26
package/test/render.test.ts
CHANGED
|
@@ -1,40 +1,43 @@
|
|
|
1
|
-
import { expect, test } from
|
|
2
|
-
import { resolveItem } from
|
|
3
|
-
import { renderItem, renderFromPayload } from
|
|
4
|
-
import { RenderError } from
|
|
5
|
-
import { encodePayload } from
|
|
6
|
-
import { FIXTURES } from
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
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(
|
|
30
|
-
const item = FIXTURES.oreT1
|
|
31
|
-
const fake = {
|
|
32
|
-
|
|
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(
|
|
36
|
-
const payload = encodePayload(FIXTURES.oreT1)
|
|
37
|
-
const { svg, item } = await renderFromPayload(payload)
|
|
38
|
-
expect(svg).toContain(
|
|
39
|
-
expect(item.itemType).toBe(
|
|
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
|
+
});
|
package/test/sanity.test.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
import { expect, test } from
|
|
2
|
-
import { VERSION } from
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { VERSION } from "../src/index.ts";
|
|
3
3
|
|
|
4
|
-
test(
|
|
5
|
-
expect(VERSION).toMatch(/^\d+\.\d+\.\d+$/)
|
|
6
|
-
})
|
|
4
|
+
test("VERSION is a semver string", () => {
|
|
5
|
+
expect(VERSION).toMatch(/^\d+\.\d+\.\d+$/);
|
|
6
|
+
});
|
package/test/sdk-link.test.ts
CHANGED
|
@@ -1,19 +1,19 @@
|
|
|
1
|
-
import { expect, test } from
|
|
2
|
-
import { resolveItem, ServerContract, type ResolvedItem } from
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { resolveItem, ServerContract, type ResolvedItem } from "@shipload/sdk";
|
|
3
3
|
|
|
4
|
-
test(
|
|
5
|
-
const resolved: ResolvedItem = resolveItem(101 /* T1 Ore */, undefined, undefined)
|
|
6
|
-
expect(resolved.itemId).toBe(101)
|
|
7
|
-
expect(resolved.category).toBe(
|
|
8
|
-
expect(resolved.itemType).toBe(
|
|
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(
|
|
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:
|
|
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
|
|
2
|
-
import { attr, el, escapeXml } from
|
|
1
|
+
import { expect, test } from "bun:test";
|
|
2
|
+
import { attr, el, escapeXml } from "../src/primitives/svg.ts";
|
|
3
3
|
|
|
4
|
-
test(
|
|
4
|
+
test("escapeXml escapes the five XML entity chars", () => {
|
|
5
5
|
expect(escapeXml(`5 > 3 & 2 < 4 "hi" 'yo'`)).toBe(
|
|
6
|
-
|
|
7
|
-
)
|
|
8
|
-
})
|
|
6
|
+
"5 > 3 & 2 < 4 "hi" 'yo'",
|
|
7
|
+
);
|
|
8
|
+
});
|
|
9
9
|
|
|
10
|
-
test(
|
|
11
|
-
expect(attr({ width: 10, height: 20, fill:
|
|
12
|
-
|
|
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(
|
|
16
|
-
expect(attr({ width: 10, height: undefined, stroke: null as unknown as string }))
|
|
17
|
-
|
|
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(
|
|
21
|
-
expect(attr({ label: `ship "fast"` })).toBe(' label="ship "fast""')
|
|
22
|
-
})
|
|
22
|
+
test("attr escapes special chars in values", () => {
|
|
23
|
+
expect(attr({ label: `ship "fast"` })).toBe(' label="ship "fast""');
|
|
24
|
+
});
|
|
23
25
|
|
|
24
|
-
test(
|
|
25
|
-
expect(el(
|
|
26
|
-
expect(el(
|
|
27
|
-
expect(el(
|
|
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
|
|
2
|
-
import { resolveItem } from
|
|
3
|
-
import { renderComponent } from
|
|
4
|
-
import { FIXTURES } from
|
|
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(
|
|
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(
|
|
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(
|
|
14
|
-
const item = FIXTURES.hullPlates
|
|
15
|
-
const resolved = resolveItem(item.item_id)
|
|
16
|
-
const svg = renderComponent(item, resolved, { mode:
|
|
17
|
-
expect(svg).toContain(
|
|
18
|
-
expect(svg).toContain(
|
|
19
|
-
expect(svg).not.toMatch(/>\d{3}<\/text>/)
|
|
20
|
-
expect(svg).toContain(
|
|
21
|
-
expect(svg).toContain(
|
|
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(
|
|
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(
|
|
32
|
-
const item = FIXTURES.hullPlates
|
|
33
|
-
const resolved = resolveItem(item.item_id)
|
|
34
|
-
const svg = renderComponent(item, resolved, { mode:
|
|
35
|
-
expect(svg).toMatchSnapshot(
|
|
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
|
|
2
|
-
import { resolveItem } from
|
|
3
|
-
import { renderByType } from
|
|
4
|
-
import { FIXTURES } from
|
|
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(
|
|
7
|
-
const item = FIXTURES.oreT1
|
|
8
|
-
const resolved = resolveItem(item.item_id)
|
|
9
|
-
const svg = renderByType(item, resolved, { mode:
|
|
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(
|
|
15
|
-
const item = FIXTURES.hullPlates
|
|
16
|
-
const resolved = resolveItem(item.item_id)
|
|
17
|
-
const svg = renderByType(item, resolved, { mode:
|
|
18
|
-
expect(svg).not.toMatch(/>\d{3,}<\/text>/)
|
|
19
|
-
expect(svg).toContain(
|
|
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(
|
|
23
|
-
const item = FIXTURES.engineT1
|
|
24
|
-
const resolved = resolveItem(item.item_id)
|
|
25
|
-
const svg = renderByType(item, resolved, { mode:
|
|
26
|
-
expect(svg).toContain(
|
|
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(
|
|
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
|
|
2
|
-
import { resolveItem } from
|
|
3
|
-
import { ITEM_HULL_PLATES, ITEM_ENGINE_T1, ITEM_SHIP_T1_PACKED } from
|
|
4
|
-
import { renderItemCell, itemCellGroup } from
|
|
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(
|
|
7
|
-
const resolved = resolveItem(ITEM_HULL_PLATES)
|
|
8
|
-
const svg = renderItemCell({ resolved, size: 48 })
|
|
9
|
-
expect(svg.startsWith(
|
|
10
|
-
expect(svg).toContain('viewBox="0 0 48 60"')
|
|
11
|
-
expect(svg.endsWith(
|
|
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(
|
|
15
|
-
const resolved = resolveItem(ITEM_HULL_PLATES)
|
|
16
|
-
const svg = renderItemCell({ resolved, size: 48 })
|
|
17
|
-
expect(svg).toContain(
|
|
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(
|
|
21
|
-
const resolved = resolveItem(ITEM_ENGINE_T1)
|
|
22
|
-
const svg = renderItemCell({ resolved, size: 48 })
|
|
23
|
-
expect(svg).toContain(
|
|
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(
|
|
27
|
-
const resolved = resolveItem(ITEM_SHIP_T1_PACKED)
|
|
28
|
-
const svg = renderItemCell({ resolved, size: 48 })
|
|
29
|
-
expect(svg).toContain(
|
|
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(
|
|
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(
|
|
40
|
-
const resolved = resolveItem(ITEM_HULL_PLATES)
|
|
41
|
-
const svg = renderItemCell({ resolved, quantity: 42, size: 48 })
|
|
42
|
-
expect(svg).toContain(
|
|
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(
|
|
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(
|
|
51
|
-
expect(svgOne).not.toContain(
|
|
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(
|
|
55
|
-
const resolved = resolveItem(ITEM_HULL_PLATES)
|
|
56
|
-
const g = itemCellGroup({ resolved, size: 48, x: 100, y: 200 })
|
|
57
|
-
expect(g.startsWith(
|
|
58
|
-
expect(g).toContain('transform="translate(100, 200)"')
|
|
59
|
-
expect(g.startsWith(
|
|
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(
|
|
63
|
-
const resolved = resolveItem(ITEM_HULL_PLATES)
|
|
64
|
-
const svg = renderItemCell({ resolved, size: 48 })
|
|
65
|
-
expect(svg).toContain(
|
|
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(
|
|
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(
|
|
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(
|
|
83
|
+
test("matches golden SVG snapshot per itemType", () => {
|
|
84
84
|
const cases: [number, string][] = [
|
|
85
|
-
[101,
|
|
86
|
-
[ITEM_HULL_PLATES,
|
|
87
|
-
[ITEM_ENGINE_T1,
|
|
88
|
-
[ITEM_SHIP_T1_PACKED,
|
|
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
|
|
2
|
-
import { resolveItem } from
|
|
3
|
-
import { renderModule } from
|
|
4
|
-
import { FIXTURES } from
|
|
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
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
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(
|
|
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(
|
|
30
|
-
expect(svg).toContain(
|
|
31
|
-
expect(svg).toContain(
|
|
32
|
-
expect(svg).toContain(
|
|
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(
|
|
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(
|
|
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(
|
|
43
|
-
const item = FIXTURES.engineT1
|
|
44
|
-
const resolved = resolveItem(item.item_id)
|
|
45
|
-
const svg = renderModule(item, resolved, { mode:
|
|
46
|
-
expect(svg).toContain(
|
|
47
|
-
expect(svg).not.toMatch(/>\d{3,}<\/(text|tspan)>/)
|
|
48
|
-
expect(svg).toContain(
|
|
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(
|
|
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(
|
|
59
|
-
const item = FIXTURES.engineT1
|
|
60
|
-
const resolved = resolveItem(item.item_id)
|
|
61
|
-
const svg = renderModule(item, resolved, { mode:
|
|
62
|
-
expect(svg).toMatchSnapshot(
|
|
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
|
+
});
|