Skip to content

Commit 365fe87

Browse files
authored
fix(model): allow system variable element bindings (#14)
1 parent 033c109 commit 365fe87

File tree

6 files changed

+137
-18
lines changed

6 files changed

+137
-18
lines changed

package.json

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
{
22
"name": "@routevn/creator-model",
3-
"version": "1.1.3",
3+
"version": "1.1.4",
44
"private": false,
55
"type": "module",
66
"license": "MIT",

src/index.js

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,3 +5,9 @@ export {
55
validatePayload,
66
validateState,
77
} from "./model.js";
8+
export {
9+
SYSTEM_VARIABLE_GROUPS,
10+
SYSTEM_VARIABLE_IDS,
11+
getSystemVariableDefinitions,
12+
isSystemVariableId,
13+
} from "./systemVariables.js";

src/model.js

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import {
1515
isPlainObject,
1616
removeTreeNode,
1717
} from "./helpers.js";
18+
import { isSystemVariableId } from "./systemVariables.js";
1819

1920
const COLLECTION_KEYS = [
2021
"scenes",
@@ -220,6 +221,15 @@ const invalidInvariant = (message, details = {}) =>
220221
details,
221222
});
222223

224+
const isVariableReferenceTarget = (state, variableId) => {
225+
if (isSystemVariableId(variableId)) {
226+
return true;
227+
}
228+
229+
const variable = state.variables.items[variableId];
230+
return isPlainObject(variable) && variable.type !== "folder";
231+
};
232+
223233
const toDomainErrorDetails = (publicError) => {
224234
const details = isPlainObject(publicError?.details)
225235
? { ...publicError.details }
@@ -3899,8 +3909,7 @@ export const assertInvariants = ({ state }) => {
38993909
elementId,
39003910
targetId,
39013911
}) => {
3902-
const variable = state.variables.items[targetId];
3903-
if (!isPlainObject(variable) || variable.type === "folder") {
3912+
if (!isVariableReferenceTarget(state, targetId)) {
39043913
return invalidInvariant(
39053914
`${ownerLabel} element variableId must reference an existing non-folder variable`,
39063915
{
@@ -6511,8 +6520,7 @@ const validateVisualElementReferenceTargets = ({
65116520
}
65126521

65136522
if (data.variableId !== undefined) {
6514-
const variable = state.variables.items[data.variableId];
6515-
if (!isPlainObject(variable) || variable.type === "folder") {
6523+
if (!isVariableReferenceTarget(state, data.variableId)) {
65166524
return invalidFromErrorFactory(
65176525
errorFactory,
65186526
`${ownerLabel} element variableId must reference an existing non-folder variable`,

src/systemVariables.js

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
export const SYSTEM_VARIABLE_GROUPS = Object.freeze([
2+
{
3+
id: "routeEngine",
4+
name: "Route Engine",
5+
variables: Object.freeze([
6+
{
7+
id: "_skipUnseenText",
8+
name: "Skip Unseen Text",
9+
scope: "global-device",
10+
type: "boolean",
11+
default: false,
12+
description:
13+
"When enabled, skip mode can continue through lines the player has not viewed yet.",
14+
},
15+
{
16+
id: "_dialogueTextSpeed",
17+
name: "Dialogue Text Speed",
18+
scope: "global-device",
19+
type: "number",
20+
default: 50,
21+
description:
22+
"Controls the default dialogue text speed stored for this device.",
23+
},
24+
]),
25+
},
26+
]);
27+
28+
export const SYSTEM_VARIABLE_IDS = Object.freeze(
29+
SYSTEM_VARIABLE_GROUPS.flatMap((group) =>
30+
(group.variables || []).map((variable) => variable.id),
31+
),
32+
);
33+
34+
const SYSTEM_VARIABLE_ID_SET = new Set(SYSTEM_VARIABLE_IDS);
35+
36+
export const isSystemVariableId = (value) => {
37+
return typeof value === "string" && SYSTEM_VARIABLE_ID_SET.has(value);
38+
};
39+
40+
export const getSystemVariableDefinitions = () => {
41+
return SYSTEM_VARIABLE_GROUPS.flatMap((group) => group.variables || []);
42+
};

tests/command-direct-coverage.test.js

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,9 @@ const createCollectionState = ({
7272
};
7373

7474
const withFontAndColorRefs = (state) => {
75-
withFiles(state, [{ id: "file-font-ui", type: "font", mimeType: "font/ttf" }]);
75+
withFiles(state, [
76+
{ id: "file-font-ui", type: "font", mimeType: "font/ttf" },
77+
]);
7678
state.fonts.items["font-ui"] = {
7779
id: "font-ui",
7880
type: "font",
@@ -1205,12 +1207,12 @@ const directCases = [
12051207
state,
12061208
command: {
12071209
type: "file.create",
1208-
payload: {
1209-
fileId: "file-a",
1210-
data: {
1211-
mimeType: "image/png",
1212-
size: 128,
1213-
sha256: "file-a-sha256",
1210+
payload: {
1211+
fileId: "file-a",
1212+
data: {
1213+
mimeType: "image/png",
1214+
size: 128,
1215+
sha256: "file-a-sha256",
12141216
},
12151217
},
12161218
},
@@ -1736,6 +1738,7 @@ const directCases = [
17361738
rotation: 0,
17371739
text: "More",
17381740
textStyleId: "text-style-ui",
1741+
variableId: "_dialogueTextSpeed",
17391742
},
17401743
},
17411744
},
@@ -1756,6 +1759,7 @@ const directCases = [
17561759
rotation: 0,
17571760
text: "More",
17581761
textStyleId: "text-style-ui",
1762+
variableId: "_dialogueTextSpeed",
17591763
});
17601764
},
17611765
runNegative: () => {
@@ -1803,16 +1807,16 @@ const directCases = [
18031807
layoutId: "layout-dialogue",
18041808
elementId: "text-a",
18051809
data: {
1806-
opacity: 0.5,
1810+
variableId: "_dialogueTextSpeed",
18071811
},
18081812
},
18091813
},
18101814
});
18111815

18121816
expect(
18131817
result.state.layouts.items["layout-dialogue"].elements.items["text-a"]
1814-
.opacity,
1815-
).toBe(0.5);
1818+
.variableId,
1819+
).toBe("_dialogueTextSpeed");
18161820
},
18171821
runNegative: () => {
18181822
expectValidation(() =>
@@ -1931,6 +1935,7 @@ const directCases = [
19311935
rotation: 0,
19321936
text: "More",
19331937
textStyleId: "text-style-ui",
1938+
variableId: "_dialogueTextSpeed",
19341939
},
19351940
},
19361941
},
@@ -1951,6 +1956,7 @@ const directCases = [
19511956
rotation: 0,
19521957
text: "More",
19531958
textStyleId: "text-style-ui",
1959+
variableId: "_dialogueTextSpeed",
19541960
});
19551961
},
19561962
runNegative: () => {
@@ -1998,16 +2004,16 @@ const directCases = [
19982004
controlId: "control-default",
19992005
elementId: "text-a",
20002006
data: {
2001-
opacity: 0.5,
2007+
variableId: "_dialogueTextSpeed",
20022008
},
20032009
},
20042010
},
20052011
});
20062012

20072013
expect(
20082014
result.state.controls.items["control-default"].elements.items["text-a"]
2009-
.opacity,
2010-
).toBe(0.5);
2015+
.variableId,
2016+
).toBe("_dialogueTextSpeed");
20112017
},
20122018
runNegative: () => {
20132019
expectValidation(() =>

tests/model-api.test.js

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,8 @@ import { expect, test } from "vitest";
33
import {
44
SCHEMA_VERSION,
55
processCommand,
6+
SYSTEM_VARIABLE_GROUPS,
7+
SYSTEM_VARIABLE_IDS,
68
validateAgainstState,
79
validatePayload,
810
validateState,
@@ -25,6 +27,14 @@ test("public api exports functions only", () => {
2527
expect(typeof processCommand).toBe("function");
2628
});
2729

30+
test("system variable ids stay aligned with the registry", () => {
31+
const registryIds = SYSTEM_VARIABLE_GROUPS.flatMap((group) =>
32+
(group.variables || []).map((variable) => variable.id),
33+
);
34+
35+
expect(SYSTEM_VARIABLE_IDS).toEqual(registryIds);
36+
});
37+
2838
test("validation functions return valid results instead of throwing", () => {
2939
expect(
3040
validatePayload({
@@ -561,6 +571,53 @@ test("validateState accepts layout elements with revealEffect", () => {
561571
});
562572
});
563573

574+
test("validateState accepts layout slider variableId refs to system variables", () => {
575+
const state = createEmptyTestState();
576+
577+
state.layouts.items["layout-ui"] = {
578+
id: "layout-ui",
579+
type: "layout",
580+
name: "UI",
581+
layoutType: "normal",
582+
elements: {
583+
items: {
584+
"slider-1": {
585+
id: "slider-1",
586+
type: "slider",
587+
name: "Slider",
588+
x: 0,
589+
y: 0,
590+
width: 400,
591+
height: 20,
592+
anchorX: 0,
593+
anchorY: 0,
594+
scaleX: 1,
595+
scaleY: 1,
596+
rotation: 0,
597+
min: 0,
598+
max: 100,
599+
step: 1,
600+
variableId: "_dialogueTextSpeed",
601+
},
602+
},
603+
tree: [
604+
{
605+
id: "slider-1",
606+
children: [],
607+
},
608+
],
609+
},
610+
};
611+
state.layouts.tree.push({
612+
id: "layout-ui",
613+
children: [],
614+
});
615+
616+
expect(validateState({ state })).toEqual({
617+
valid: true,
618+
});
619+
});
620+
564621
test("validateState rejects legacy layout element style overrides", () => {
565622
const state = createEmptyTestState();
566623

0 commit comments

Comments
 (0)