Skip to content

Commit cd878b1

Browse files
committed
fix(construction): align furniture pages with BN semantics
Show furniture constructions from post_furniture and remove legacy f_ prefix inference across construction views. Add focused regression tests and verify the furniture construction path in the browser. Refs #135
1 parent 8a5fe30 commit cd878b1

File tree

9 files changed

+283
-55
lines changed

9 files changed

+283
-55
lines changed

src/types.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -97,8 +97,10 @@ export type Construction = {
9797
)[];
9898
pre_note?: string;
9999

100-
pre_terrain?: string; // if starts with f_, then furniture_id, else terrain_id
101-
post_terrain?: string; // as above
100+
pre_terrain?: string;
101+
pre_furniture?: string;
102+
post_terrain?: string;
103+
post_furniture?: string;
102104

103105
pre_flags?: string | string[];
104106
post_flags?: string[];

src/types/Construction.svelte

Lines changed: 32 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,11 @@ import JsonView from "../JsonView.svelte";
55
import { getContext, untrack } from "svelte";
66
import { CBNData } from "../data";
77
import type { Construction, RequirementData } from "../types";
8+
import {
9+
getConstructionPrerequisites,
10+
getConstructionResults,
11+
isNullConstructionResult,
12+
} from "./construction";
813
import ItemLink from "./ItemLink.svelte";
914
import RequirementDataTools from "./item/RequirementDataTools.svelte";
1015
import { i18n, gameSingular, gameSingularName } from "../utils/i18n";
@@ -42,6 +47,8 @@ const components = requirements.flatMap(([req, count]) => {
4247
const byproducts = data.flattenItemGroup(
4348
data.normalizeItemGroup(construction.byproducts, "collection"),
4449
);
50+
const prerequisites = getConstructionPrerequisites(construction);
51+
const results = getConstructionResults(construction);
4552
4653
const preFlags: { flag: string; force_terrain?: boolean }[] = [];
4754
if (construction.pre_flags)
@@ -71,14 +78,16 @@ if (construction.pre_flags)
7178
? `${construction.time} m`
7279
: (construction.time ?? "0 m")}
7380
</dd>
74-
{#if construction.pre_terrain}
81+
{#if prerequisites.length}
7582
<dt>{t("Requires", { _context })}</dt>
7683
<dd>
77-
<ItemLink
78-
type={construction.pre_terrain.startsWith("f_")
79-
? "furniture"
80-
: "terrain"}
81-
id={construction.pre_terrain} />
84+
<ul class="comma-separated">
85+
{#each prerequisites as prerequisite}
86+
<li>
87+
<ItemLink type={prerequisite.type} id={prerequisite.id} />
88+
</li>
89+
{/each}
90+
</ul>
8291
</dd>
8392
{/if}
8493
{#if preFlags.length}
@@ -118,23 +127,25 @@ if (construction.pre_flags)
118127
</ul>
119128
</dd>
120129
{/if}
121-
{#if !includeTitle && construction.post_terrain}
130+
{#if !includeTitle && results.length}
122131
<dt>{t("Creates", { _context })}</dt>
123132
<dd>
124-
{#if construction.post_terrain === "f_null"}
125-
<em
126-
>{t("nothing", {
127-
_context,
128-
_comment:
129-
'The furniture/terrain "created" by a deconstruction is...',
130-
})}</em>
131-
{:else}
132-
<ItemLink
133-
type={construction.post_terrain.startsWith("f_")
134-
? "furniture"
135-
: "terrain"}
136-
id={construction.post_terrain} />
137-
{/if}
133+
<ul class="comma-separated">
134+
{#each results as result}
135+
<li>
136+
{#if isNullConstructionResult(result)}
137+
<em
138+
>{t("nothing", {
139+
_context,
140+
_comment:
141+
'The furniture/terrain "created" by a deconstruction is...',
142+
})}</em>
143+
{:else}
144+
<ItemLink type={result.type} id={result.id} />
145+
{/if}
146+
</li>
147+
{/each}
148+
</ul>
138149
</dd>
139150
{/if}
140151
</dl>

src/types/Construction.test.ts

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,92 @@
1+
/**
2+
* @vitest-environment happy-dom
3+
*/
4+
import { render } from "@testing-library/svelte";
5+
import { describe, expect, it } from "vitest";
6+
import WithData from "../WithData.svelte";
7+
import { CBNData } from "../data";
8+
import Construction from "./Construction.svelte";
9+
10+
describe("Construction", () => {
11+
it("renders furniture prerequisites and null furniture results", () => {
12+
const data = new CBNData([
13+
{
14+
type: "construction_group",
15+
id: "advanced_object_deconstruction",
16+
name: "Advanced Object Deconstruction",
17+
},
18+
{
19+
type: "construction",
20+
id: "constr_remove_object_fireplace",
21+
group: "advanced_object_deconstruction",
22+
category: "OTHER",
23+
time: "90 m",
24+
required_skills: [["fabrication", 2]],
25+
pre_furniture: "f_fireplace",
26+
post_furniture: "f_null",
27+
},
28+
{
29+
type: "furniture",
30+
id: "f_fireplace",
31+
name: "fireplace",
32+
description: "A warm test fixture.",
33+
move_cost_mod: 0,
34+
required_str: 0,
35+
},
36+
]);
37+
38+
const { getByText } = render(WithData, {
39+
Component: Construction,
40+
data,
41+
construction: data.byId("construction", "constr_remove_object_fireplace"),
42+
});
43+
44+
expect(getByText("Requires")).toBeTruthy();
45+
expect(getByText("fireplace")).toBeTruthy();
46+
expect(getByText("Creates")).toBeTruthy();
47+
expect(getByText("nothing")).toBeTruthy();
48+
});
49+
50+
it("renders terrain prerequisites and furniture results without prefix inference", () => {
51+
const data = new CBNData([
52+
{
53+
type: "construction_group",
54+
id: "build_beaded_door",
55+
name: "Build Beaded Door",
56+
},
57+
{
58+
type: "construction",
59+
id: "constr_beaded_door",
60+
group: "build_beaded_door",
61+
category: "OTHER",
62+
time: "30 m",
63+
required_skills: [["fabrication", 1]],
64+
pre_terrain: "t_door_frame",
65+
post_furniture: "f_beaded_door",
66+
},
67+
{
68+
type: "terrain",
69+
id: "t_door_frame",
70+
name: "door frame",
71+
description: "A test door frame.",
72+
},
73+
{
74+
type: "furniture",
75+
id: "f_beaded_door",
76+
name: "beaded door",
77+
description: "A test beaded door.",
78+
move_cost_mod: 0,
79+
required_str: 0,
80+
},
81+
]);
82+
83+
const { getByText } = render(WithData, {
84+
Component: Construction,
85+
data,
86+
construction: data.byId("construction", "constr_beaded_door"),
87+
});
88+
89+
expect(getByText("door frame")).toBeTruthy();
90+
expect(getByText("beaded door")).toBeTruthy();
91+
});
92+
});

src/types/Furniture.svelte

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ const bash = item.bash?.items
4444
4545
const constructions = data
4646
.byType("construction")
47-
.filter((c) => c.post_terrain === item.id);
47+
.filter((c) => c.post_furniture === item.id);
4848
4949
const bashedFrom = data
5050
.byType("furniture")

src/types/Furniture.test.ts

Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
/**
2+
* @vitest-environment happy-dom
3+
*/
4+
import { render } from "@testing-library/svelte";
5+
import { describe, expect, it } from "vitest";
6+
import WithData from "../WithData.svelte";
7+
import { CBNData } from "../data";
8+
import Furniture from "./Furniture.svelte";
9+
10+
describe("Furniture", () => {
11+
it("shows furniture constructions from post_furniture", () => {
12+
const data = new CBNData([
13+
{
14+
type: "construction_group",
15+
id: "build_sign",
16+
name: "Build Sign",
17+
},
18+
{
19+
type: "construction_group",
20+
id: "dig_pit",
21+
name: "Dig Pit",
22+
},
23+
{
24+
type: "construction",
25+
id: "constr_sign",
26+
group: "build_sign",
27+
category: "OTHER",
28+
time: "20 m",
29+
required_skills: [["fabrication", 0]],
30+
post_furniture: "f_sign",
31+
},
32+
{
33+
type: "construction",
34+
id: "constr_pit",
35+
group: "dig_pit",
36+
category: "OTHER",
37+
time: "30 m",
38+
required_skills: [["fabrication", 0]],
39+
post_terrain: "t_pit",
40+
},
41+
{
42+
type: "furniture",
43+
id: "f_sign",
44+
name: "sign",
45+
description: "Read it. Warnings ahead.",
46+
move_cost_mod: 0,
47+
required_str: 0,
48+
},
49+
{
50+
type: "terrain",
51+
id: "t_pit",
52+
name: "pit",
53+
description: "A test pit.",
54+
},
55+
]);
56+
57+
const { getByText, queryByText } = render(WithData, {
58+
Component: Furniture,
59+
data,
60+
item: data.byId("furniture", "f_sign"),
61+
});
62+
63+
expect(getByText("Construction")).toBeTruthy();
64+
expect(getByText("Build Sign")).toBeTruthy();
65+
expect(queryByText("Dig Pit")).toBeNull();
66+
});
67+
});

src/types/ToolQuality.svelte

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,9 @@ import { getContext, untrack } from "svelte";
55
import { CBNData } from "../data";
66
import LimitedList from "../LimitedList.svelte";
77
import type { Construction, Item, ToolQuality, VehiclePart } from "../types";
8+
import { getConstructionPrerequisites } from "./construction";
89
import ItemLink from "./ItemLink.svelte";
9-
import { byName, i18n, gameSingularName } from "../utils/i18n";
10+
import { byName, gameSingularName } from "../utils/i18n";
1011
1112
interface Props {
1213
item: ToolQuality;
@@ -206,13 +207,13 @@ constructionsUsingQualityByLevelList.forEach(([, constructions]) => {
206207
id={f.group}
207208
type="construction_group"
208209
showIcon={false} />
209-
{#if f.pre_terrain}
210-
on {#each [f.pre_terrain].flat() as preTerrain, i}
211-
{@const itemType = preTerrain.startsWith("f_")
212-
? "furniture"
213-
: "terrain"}
214-
{#if i !== 0}{i18n.__(" OR ")}{/if}
215-
<ItemLink type={itemType} id={preTerrain} />
210+
{@const prerequisites = getConstructionPrerequisites(f)}
211+
{#if prerequisites.length}
212+
{t("on")}
213+
{#each prerequisites as prerequisite, i}
214+
{#if i !== 0},
215+
{/if}
216+
<ItemLink type={prerequisite.type} id={prerequisite.id} />
216217
{/each}
217218
{/if}
218219
{/snippet}

src/types/construction.ts

Lines changed: 53 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
import type { Construction } from "../types";
2+
3+
export type ConstructionSurfaceTarget = {
4+
id: string;
5+
type: "terrain" | "furniture";
6+
};
7+
8+
/**
9+
* Collect prerequisite terrain/furniture targets using explicit fields.
10+
*/
11+
export function getConstructionPrerequisites(
12+
construction: Pick<Construction, "pre_terrain" | "pre_furniture">,
13+
): ConstructionSurfaceTarget[] {
14+
const prerequisites: ConstructionSurfaceTarget[] = [];
15+
16+
if (construction.pre_terrain) {
17+
prerequisites.push({ id: construction.pre_terrain, type: "terrain" });
18+
}
19+
20+
if (construction.pre_furniture) {
21+
prerequisites.push({ id: construction.pre_furniture, type: "furniture" });
22+
}
23+
24+
return prerequisites;
25+
}
26+
27+
/**
28+
* Collect output terrain/furniture targets using explicit fields.
29+
*/
30+
export function getConstructionResults(
31+
construction: Pick<Construction, "post_terrain" | "post_furniture">,
32+
): ConstructionSurfaceTarget[] {
33+
const results: ConstructionSurfaceTarget[] = [];
34+
35+
if (construction.post_terrain) {
36+
results.push({ id: construction.post_terrain, type: "terrain" });
37+
}
38+
39+
if (construction.post_furniture) {
40+
results.push({ id: construction.post_furniture, type: "furniture" });
41+
}
42+
43+
return results;
44+
}
45+
46+
export function isNullConstructionResult(
47+
result: ConstructionSurfaceTarget,
48+
): boolean {
49+
return (
50+
(result.type === "terrain" && result.id === "t_null") ||
51+
(result.type === "furniture" && result.id === "f_null")
52+
);
53+
}

0 commit comments

Comments
 (0)