@player-tools/fluent 0.12.1--canary.241.6077
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/dist/cjs/index.cjs +2396 -0
- package/dist/cjs/index.cjs.map +1 -0
- package/dist/index.legacy-esm.js +2276 -0
- package/dist/index.mjs +2276 -0
- package/dist/index.mjs.map +1 -0
- package/package.json +38 -0
- package/src/core/base-builder/__tests__/fluent-builder-base.test.ts +2423 -0
- package/src/core/base-builder/__tests__/fluent-partial.test.ts +179 -0
- package/src/core/base-builder/__tests__/id-generator.test.ts +658 -0
- package/src/core/base-builder/__tests__/registry.test.ts +534 -0
- package/src/core/base-builder/__tests__/resolution-mixed-arrays.test.ts +319 -0
- package/src/core/base-builder/__tests__/resolution-pipeline.test.ts +416 -0
- package/src/core/base-builder/__tests__/resolution-switches.test.ts +468 -0
- package/src/core/base-builder/__tests__/resolution-templates.test.ts +255 -0
- package/src/core/base-builder/__tests__/switch.test.ts +815 -0
- package/src/core/base-builder/__tests__/template.test.ts +596 -0
- package/src/core/base-builder/__tests__/value-extraction.test.ts +200 -0
- package/src/core/base-builder/__tests__/value-storage.test.ts +459 -0
- package/src/core/base-builder/conditional/index.ts +64 -0
- package/src/core/base-builder/context.ts +152 -0
- package/src/core/base-builder/errors.ts +69 -0
- package/src/core/base-builder/fluent-builder-base.ts +308 -0
- package/src/core/base-builder/guards.ts +137 -0
- package/src/core/base-builder/id/generator.ts +290 -0
- package/src/core/base-builder/id/registry.ts +152 -0
- package/src/core/base-builder/index.ts +72 -0
- package/src/core/base-builder/resolution/path-resolver.ts +116 -0
- package/src/core/base-builder/resolution/pipeline.ts +103 -0
- package/src/core/base-builder/resolution/steps/__tests__/nested-asset-wrappers.test.ts +206 -0
- package/src/core/base-builder/resolution/steps/asset-id.ts +77 -0
- package/src/core/base-builder/resolution/steps/asset-wrappers.ts +64 -0
- package/src/core/base-builder/resolution/steps/builders.ts +84 -0
- package/src/core/base-builder/resolution/steps/mixed-arrays.ts +95 -0
- package/src/core/base-builder/resolution/steps/nested-asset-wrappers.ts +124 -0
- package/src/core/base-builder/resolution/steps/static-values.ts +35 -0
- package/src/core/base-builder/resolution/steps/switches.ts +71 -0
- package/src/core/base-builder/resolution/steps/templates.ts +40 -0
- package/src/core/base-builder/resolution/value-resolver.ts +333 -0
- package/src/core/base-builder/storage/auxiliary-storage.ts +82 -0
- package/src/core/base-builder/storage/value-storage.ts +282 -0
- package/src/core/base-builder/types.ts +266 -0
- package/src/core/base-builder/utils.ts +10 -0
- package/src/core/flow/__tests__/index.test.ts +292 -0
- package/src/core/flow/index.ts +118 -0
- package/src/core/index.ts +8 -0
- package/src/core/mocks/generated/action.builder.ts +92 -0
- package/src/core/mocks/generated/choice-item.builder.ts +120 -0
- package/src/core/mocks/generated/choice.builder.ts +134 -0
- package/src/core/mocks/generated/collection.builder.ts +93 -0
- package/src/core/mocks/generated/field-collection.builder.ts +86 -0
- package/src/core/mocks/generated/index.ts +10 -0
- package/src/core/mocks/generated/info.builder.ts +64 -0
- package/src/core/mocks/generated/input.builder.ts +63 -0
- package/src/core/mocks/generated/overview-collection.builder.ts +65 -0
- package/src/core/mocks/generated/splash-collection.builder.ts +93 -0
- package/src/core/mocks/generated/text.builder.ts +47 -0
- package/src/core/mocks/index.ts +1 -0
- package/src/core/mocks/types/action.ts +92 -0
- package/src/core/mocks/types/choice.ts +129 -0
- package/src/core/mocks/types/collection.ts +140 -0
- package/src/core/mocks/types/info.ts +7 -0
- package/src/core/mocks/types/input.ts +7 -0
- package/src/core/mocks/types/text.ts +5 -0
- package/src/core/schema/__tests__/index.test.ts +127 -0
- package/src/core/schema/index.ts +195 -0
- package/src/core/schema/types.ts +7 -0
- package/src/core/switch/__tests__/index.test.ts +156 -0
- package/src/core/switch/index.ts +81 -0
- package/src/core/tagged-template/README.md +448 -0
- package/src/core/tagged-template/__tests__/extract-bindings-from-schema.test.ts +207 -0
- package/src/core/tagged-template/__tests__/index.test.ts +190 -0
- package/src/core/tagged-template/__tests__/schema-std-integration.test.ts +580 -0
- package/src/core/tagged-template/binding.ts +95 -0
- package/src/core/tagged-template/expression.ts +92 -0
- package/src/core/tagged-template/extract-bindings-from-schema.ts +120 -0
- package/src/core/tagged-template/index.ts +5 -0
- package/src/core/tagged-template/std.ts +472 -0
- package/src/core/tagged-template/types.ts +123 -0
- package/src/core/template/__tests__/index.test.ts +380 -0
- package/src/core/template/index.ts +196 -0
- package/src/core/utils/index.ts +160 -0
- package/src/fp/README.md +411 -0
- package/src/fp/__tests__/index.test.ts +1178 -0
- package/src/fp/index.ts +386 -0
- package/src/gen/common.ts +15 -0
- package/src/index.ts +5 -0
- package/src/types.ts +203 -0
- package/types/core/base-builder/conditional/index.d.ts +21 -0
- package/types/core/base-builder/context.d.ts +39 -0
- package/types/core/base-builder/errors.d.ts +45 -0
- package/types/core/base-builder/fluent-builder-base.d.ts +147 -0
- package/types/core/base-builder/guards.d.ts +58 -0
- package/types/core/base-builder/id/generator.d.ts +69 -0
- package/types/core/base-builder/id/registry.d.ts +93 -0
- package/types/core/base-builder/index.d.ts +9 -0
- package/types/core/base-builder/resolution/path-resolver.d.ts +15 -0
- package/types/core/base-builder/resolution/pipeline.d.ts +27 -0
- package/types/core/base-builder/resolution/steps/asset-id.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/asset-wrappers.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/builders.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/mixed-arrays.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/nested-asset-wrappers.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/static-values.d.ts +14 -0
- package/types/core/base-builder/resolution/steps/switches.d.ts +15 -0
- package/types/core/base-builder/resolution/steps/templates.d.ts +14 -0
- package/types/core/base-builder/resolution/value-resolver.d.ts +62 -0
- package/types/core/base-builder/storage/auxiliary-storage.d.ts +50 -0
- package/types/core/base-builder/storage/value-storage.d.ts +82 -0
- package/types/core/base-builder/types.d.ts +183 -0
- package/types/core/base-builder/utils.d.ts +2 -0
- package/types/core/flow/index.d.ts +23 -0
- package/types/core/index.d.ts +8 -0
- package/types/core/mocks/index.d.ts +2 -0
- package/types/core/mocks/types/action.d.ts +58 -0
- package/types/core/mocks/types/choice.d.ts +95 -0
- package/types/core/mocks/types/collection.d.ts +102 -0
- package/types/core/mocks/types/info.d.ts +7 -0
- package/types/core/mocks/types/input.d.ts +7 -0
- package/types/core/mocks/types/text.d.ts +5 -0
- package/types/core/schema/index.d.ts +34 -0
- package/types/core/schema/types.d.ts +5 -0
- package/types/core/switch/index.d.ts +21 -0
- package/types/core/tagged-template/binding.d.ts +19 -0
- package/types/core/tagged-template/expression.d.ts +11 -0
- package/types/core/tagged-template/extract-bindings-from-schema.d.ts +7 -0
- package/types/core/tagged-template/index.d.ts +6 -0
- package/types/core/tagged-template/std.d.ts +174 -0
- package/types/core/tagged-template/types.d.ts +69 -0
- package/types/core/template/index.d.ts +97 -0
- package/types/core/utils/index.d.ts +47 -0
- package/types/fp/index.d.ts +149 -0
- package/types/gen/common.d.ts +6 -0
- package/types/index.d.ts +3 -0
- package/types/types.d.ts +163 -0
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach } from "vitest";
|
|
2
|
+
import { action } from "../../mocks/generated";
|
|
3
|
+
import { binding as b, expression as e } from "../../tagged-template";
|
|
4
|
+
import { resetGlobalIdSet } from "../index";
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Test suite for value extraction with TaggedTemplateValue
|
|
8
|
+
* Ensures that arrays and objects with TaggedTemplateValue are properly extracted
|
|
9
|
+
* without being stringified to [object Object] or flattened
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
describe("Value Extraction - TaggedTemplateValue Handling", () => {
|
|
13
|
+
beforeEach(() => {
|
|
14
|
+
resetGlobalIdSet();
|
|
15
|
+
});
|
|
16
|
+
|
|
17
|
+
test("preserves array structure with TaggedTemplateValue elements", () => {
|
|
18
|
+
const asset = action()
|
|
19
|
+
.withExp([
|
|
20
|
+
e`conditional(isEmpty({{forms.test}}), setDataVal('forms.test', ''), 'false')`,
|
|
21
|
+
e`conditional(isEmpty({{forms.other}}), setDataVal('forms.other', ''), 'false')`,
|
|
22
|
+
])
|
|
23
|
+
.build({ parentId: "view-1" });
|
|
24
|
+
|
|
25
|
+
expect(asset.exp).toBeInstanceOf(Array);
|
|
26
|
+
expect(asset.exp).toHaveLength(2);
|
|
27
|
+
// Expressions should preserve @[]@ syntax
|
|
28
|
+
expect(asset.exp?.[0]).toBe(
|
|
29
|
+
"@[conditional(isEmpty({{forms.test}}), setDataVal('forms.test', ''), 'false')]@",
|
|
30
|
+
);
|
|
31
|
+
expect(asset.exp?.[1]).toBe(
|
|
32
|
+
"@[conditional(isEmpty({{forms.other}}), setDataVal('forms.other', ''), 'false')]@",
|
|
33
|
+
);
|
|
34
|
+
});
|
|
35
|
+
|
|
36
|
+
test("preserves array structure with mixed TaggedTemplateValue and strings", () => {
|
|
37
|
+
const asset = action()
|
|
38
|
+
.withExp([
|
|
39
|
+
"static string",
|
|
40
|
+
e`conditional(isEmpty({{forms.test}}), setDataVal('forms.test', ''), 'false')`,
|
|
41
|
+
])
|
|
42
|
+
.build({ parentId: "view-1" });
|
|
43
|
+
|
|
44
|
+
expect(asset.exp).toBeInstanceOf(Array);
|
|
45
|
+
expect(asset.exp).toHaveLength(2);
|
|
46
|
+
expect(asset.exp?.[0]).toBe("static string");
|
|
47
|
+
// Expression should preserve @[]@ syntax
|
|
48
|
+
expect(asset.exp?.[1]).toBe(
|
|
49
|
+
"@[conditional(isEmpty({{forms.test}}), setDataVal('forms.test', ''), 'false')]@",
|
|
50
|
+
);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("extracts object with TaggedTemplateValue properties correctly", () => {
|
|
54
|
+
const asset = action()
|
|
55
|
+
.withConfirmation({
|
|
56
|
+
message: b`forms.confirmMessage`,
|
|
57
|
+
affirmativeLabel: b`forms.yesLabel`,
|
|
58
|
+
negativeLabel: "Cancel",
|
|
59
|
+
})
|
|
60
|
+
.build({ parentId: "view-1" });
|
|
61
|
+
|
|
62
|
+
// Bindings should preserve {{}} syntax
|
|
63
|
+
expect(asset.confirmation).toEqual({
|
|
64
|
+
message: "{{forms.confirmMessage}}",
|
|
65
|
+
affirmativeLabel: "{{forms.yesLabel}}",
|
|
66
|
+
negativeLabel: "Cancel",
|
|
67
|
+
});
|
|
68
|
+
|
|
69
|
+
// Ensure it's not stringified to [object Object]
|
|
70
|
+
expect(typeof asset.confirmation).toBe("object");
|
|
71
|
+
expect(asset.confirmation).not.toBe("[object Object]");
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
test("handles nested arrays in objects with TaggedTemplateValue", () => {
|
|
75
|
+
// Simulate a complex structure with nested arrays
|
|
76
|
+
const asset = action()
|
|
77
|
+
.withAdditionalProperties({
|
|
78
|
+
listeners: {
|
|
79
|
+
"dataChange.forms.test": [
|
|
80
|
+
e`conditional(isEmpty({{forms.test}}), setDataVal('forms.test', ''), 'false')`,
|
|
81
|
+
],
|
|
82
|
+
"dataChange.forms.other": [
|
|
83
|
+
e`setDataVal('forms.other', 'value1')`,
|
|
84
|
+
e`setDataVal('forms.other2', 'value2')`,
|
|
85
|
+
],
|
|
86
|
+
},
|
|
87
|
+
})
|
|
88
|
+
.build({ parentId: "view-1" });
|
|
89
|
+
|
|
90
|
+
// Check listeners object exists and is not stringified
|
|
91
|
+
expect(asset.listeners).toBeDefined();
|
|
92
|
+
expect(typeof asset.listeners).toBe("object");
|
|
93
|
+
expect(asset.listeners).not.toBe("[object Object]");
|
|
94
|
+
|
|
95
|
+
// Check nested arrays are preserved with expression syntax
|
|
96
|
+
const listeners = asset.listeners as Record<string, string[]>;
|
|
97
|
+
expect(Array.isArray(listeners["dataChange.forms.test"])).toBe(true);
|
|
98
|
+
expect(listeners["dataChange.forms.test"]).toHaveLength(1);
|
|
99
|
+
expect(listeners["dataChange.forms.test"][0]).toBe(
|
|
100
|
+
"@[conditional(isEmpty({{forms.test}}), setDataVal('forms.test', ''), 'false')]@",
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
expect(Array.isArray(listeners["dataChange.forms.other"])).toBe(true);
|
|
104
|
+
expect(listeners["dataChange.forms.other"]).toHaveLength(2);
|
|
105
|
+
expect(listeners["dataChange.forms.other"][0]).toBe(
|
|
106
|
+
"@[setDataVal('forms.other', 'value1')]@",
|
|
107
|
+
);
|
|
108
|
+
expect(listeners["dataChange.forms.other"][1]).toBe(
|
|
109
|
+
"@[setDataVal('forms.other2', 'value2')]@",
|
|
110
|
+
);
|
|
111
|
+
});
|
|
112
|
+
|
|
113
|
+
test("handles nested objects with TaggedTemplateValue in arrays", () => {
|
|
114
|
+
// Simulate modifiers array with objects
|
|
115
|
+
const asset = action()
|
|
116
|
+
.withAdditionalProperties({
|
|
117
|
+
modifiers: [
|
|
118
|
+
{ type: "tag", value: b`forms.tagValue` },
|
|
119
|
+
{ type: "style", value: "important" },
|
|
120
|
+
],
|
|
121
|
+
})
|
|
122
|
+
.build({ parentId: "view-1" });
|
|
123
|
+
|
|
124
|
+
expect(asset.modifiers).toBeDefined();
|
|
125
|
+
expect(Array.isArray(asset.modifiers)).toBe(true);
|
|
126
|
+
|
|
127
|
+
const modifiers = asset.modifiers as Array<{ type: string; value: string }>;
|
|
128
|
+
expect(modifiers).toHaveLength(2);
|
|
129
|
+
|
|
130
|
+
// First modifier should have extracted TaggedTemplateValue with binding syntax
|
|
131
|
+
expect(modifiers[0]).toEqual({ type: "tag", value: "{{forms.tagValue}}" });
|
|
132
|
+
expect(modifiers[0]).not.toBe("[object Object]");
|
|
133
|
+
|
|
134
|
+
// Second modifier should be unchanged
|
|
135
|
+
expect(modifiers[1]).toEqual({ type: "style", value: "important" });
|
|
136
|
+
expect(modifiers[1]).not.toBe("[object Object]");
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("handles empty metadata object correctly", () => {
|
|
140
|
+
const asset = action().withMetaData({}).build({ parentId: "view-1" });
|
|
141
|
+
|
|
142
|
+
expect(asset.metaData).toEqual({});
|
|
143
|
+
expect(asset.metaData).not.toBe("[object Object]");
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
test("handles metadata object with plain values correctly", () => {
|
|
147
|
+
const asset = action()
|
|
148
|
+
.withMetaData({
|
|
149
|
+
role: "primary",
|
|
150
|
+
size: "large",
|
|
151
|
+
})
|
|
152
|
+
.build({ parentId: "view-1" });
|
|
153
|
+
|
|
154
|
+
expect(asset.metaData).toEqual({
|
|
155
|
+
role: "primary",
|
|
156
|
+
size: "large",
|
|
157
|
+
});
|
|
158
|
+
expect(asset.metaData).not.toBe("[object Object]");
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
test("deeply nested structures with TaggedTemplateValue", () => {
|
|
162
|
+
const asset = action()
|
|
163
|
+
.withAdditionalProperties({
|
|
164
|
+
config: {
|
|
165
|
+
validation: {
|
|
166
|
+
rules: [
|
|
167
|
+
e`conditional(isEmpty({{forms.field1}}), 'required', 'valid')`,
|
|
168
|
+
e`conditional(isEmpty({{forms.field2}}), 'required', 'valid')`,
|
|
169
|
+
],
|
|
170
|
+
messages: {
|
|
171
|
+
error: b`forms.errorMessage`,
|
|
172
|
+
warning: "Please check your input",
|
|
173
|
+
},
|
|
174
|
+
},
|
|
175
|
+
},
|
|
176
|
+
})
|
|
177
|
+
.build({ parentId: "view-1" });
|
|
178
|
+
|
|
179
|
+
const config = asset.config as {
|
|
180
|
+
validation: {
|
|
181
|
+
rules: string[];
|
|
182
|
+
messages: { error: string; warning: string };
|
|
183
|
+
};
|
|
184
|
+
};
|
|
185
|
+
|
|
186
|
+
// Check deeply nested arrays with expression syntax
|
|
187
|
+
expect(Array.isArray(config.validation.rules)).toBe(true);
|
|
188
|
+
expect(config.validation.rules).toHaveLength(2);
|
|
189
|
+
expect(config.validation.rules[0]).toBe(
|
|
190
|
+
"@[conditional(isEmpty({{forms.field1}}), 'required', 'valid')]@",
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
// Check deeply nested objects with binding syntax
|
|
194
|
+
expect(config.validation.messages).toEqual({
|
|
195
|
+
error: "{{forms.errorMessage}}",
|
|
196
|
+
warning: "Please check your input",
|
|
197
|
+
});
|
|
198
|
+
expect(config.validation.messages).not.toBe("[object Object]");
|
|
199
|
+
});
|
|
200
|
+
});
|
|
@@ -0,0 +1,459 @@
|
|
|
1
|
+
import { describe, test, expect, beforeEach, vi } from "vitest";
|
|
2
|
+
import type { BaseBuildContext, FluentBuilder } from "../types";
|
|
3
|
+
import { FLUENT_BUILDER_SYMBOL } from "../types";
|
|
4
|
+
import { ValueStorage } from "../storage/value-storage";
|
|
5
|
+
|
|
6
|
+
// Create a mock builder that satisfies the FluentBuilder interface
|
|
7
|
+
function createMockBuilder<T>(
|
|
8
|
+
buildResult: T,
|
|
9
|
+
): FluentBuilder<T, BaseBuildContext> {
|
|
10
|
+
return {
|
|
11
|
+
[FLUENT_BUILDER_SYMBOL]: true as const,
|
|
12
|
+
build: vi.fn(() => buildResult),
|
|
13
|
+
} as unknown as FluentBuilder<T, BaseBuildContext>;
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
interface TestType {
|
|
17
|
+
value?: string;
|
|
18
|
+
count?: number;
|
|
19
|
+
items?: string[];
|
|
20
|
+
label?: string;
|
|
21
|
+
nested?: { deep: string };
|
|
22
|
+
builder?: { id: string };
|
|
23
|
+
mixedArray?: unknown[];
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
describe("ValueStorage - set() Routing", () => {
|
|
27
|
+
let storage: ValueStorage<TestType>;
|
|
28
|
+
|
|
29
|
+
beforeEach(() => {
|
|
30
|
+
storage = new ValueStorage<TestType>();
|
|
31
|
+
});
|
|
32
|
+
|
|
33
|
+
test("routes FluentBuilder to builders Map", () => {
|
|
34
|
+
const builder = createMockBuilder({ id: "test", type: "text" });
|
|
35
|
+
|
|
36
|
+
storage.set("builder", builder);
|
|
37
|
+
|
|
38
|
+
expect(storage.getValueType("builder")).toBe("builder");
|
|
39
|
+
expect(storage.getBuilders().has("builder")).toBe(true);
|
|
40
|
+
expect(storage.peek("builder")).toBeUndefined();
|
|
41
|
+
expect(storage.peekBuilder("builder")).toBeDefined();
|
|
42
|
+
});
|
|
43
|
+
|
|
44
|
+
test("routes arrays with builders to mixedArrays Map", () => {
|
|
45
|
+
const builder = createMockBuilder({ id: "test", type: "text" });
|
|
46
|
+
|
|
47
|
+
storage.set("mixedArray", [builder, "static"]);
|
|
48
|
+
|
|
49
|
+
expect(storage.getValueType("mixedArray")).toBe("mixed-array");
|
|
50
|
+
expect(storage.getMixedArrays().has("mixedArray")).toBe(true);
|
|
51
|
+
});
|
|
52
|
+
|
|
53
|
+
test("routes objects containing builders to builders Map", () => {
|
|
54
|
+
const builder = createMockBuilder({ id: "test", type: "text" });
|
|
55
|
+
const objWithBuilder = { asset: builder };
|
|
56
|
+
|
|
57
|
+
storage.set("nested", objWithBuilder as unknown as { deep: string });
|
|
58
|
+
|
|
59
|
+
expect(storage.getValueType("nested")).toBe("builder");
|
|
60
|
+
expect(storage.getBuilders().has("nested")).toBe(true);
|
|
61
|
+
});
|
|
62
|
+
|
|
63
|
+
test("routes static values to values object", () => {
|
|
64
|
+
storage.set("value", "hello");
|
|
65
|
+
storage.set("count", 42);
|
|
66
|
+
storage.set("items", ["a", "b", "c"]);
|
|
67
|
+
storage.set("nested", { deep: "value" });
|
|
68
|
+
|
|
69
|
+
expect(storage.getValueType("value")).toBe("static");
|
|
70
|
+
expect(storage.getValueType("count")).toBe("static");
|
|
71
|
+
expect(storage.getValueType("items")).toBe("static");
|
|
72
|
+
expect(storage.getValueType("nested")).toBe("static");
|
|
73
|
+
|
|
74
|
+
expect(storage.peek("value")).toBe("hello");
|
|
75
|
+
expect(storage.peek("count")).toBe(42);
|
|
76
|
+
expect(storage.peek("items")).toEqual(["a", "b", "c"]);
|
|
77
|
+
expect(storage.peek("nested")).toEqual({ deep: "value" });
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("clears previous storage type when setting new value", () => {
|
|
81
|
+
const builder = createMockBuilder({ id: "test" });
|
|
82
|
+
|
|
83
|
+
// First set as builder
|
|
84
|
+
storage.set("value", builder as unknown as string);
|
|
85
|
+
expect(storage.getValueType("value")).toBe("builder");
|
|
86
|
+
|
|
87
|
+
// Then set as static
|
|
88
|
+
storage.set("value", "static");
|
|
89
|
+
expect(storage.getValueType("value")).toBe("static");
|
|
90
|
+
expect(storage.getBuilders().has("value")).toBe(false);
|
|
91
|
+
|
|
92
|
+
// Then set as mixed array
|
|
93
|
+
storage.set("value", [builder, "mixed"] as unknown as string);
|
|
94
|
+
expect(storage.getValueType("value")).toBe("mixed-array");
|
|
95
|
+
expect(storage.getValues().value).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
describe("ValueStorage - containsBuilder() Detection", () => {
|
|
100
|
+
let storage: ValueStorage<TestType>;
|
|
101
|
+
|
|
102
|
+
beforeEach(() => {
|
|
103
|
+
storage = new ValueStorage<TestType>();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
test("detects direct FluentBuilder", () => {
|
|
107
|
+
const builder = createMockBuilder({ id: "test" });
|
|
108
|
+
|
|
109
|
+
storage.set("builder", builder);
|
|
110
|
+
|
|
111
|
+
expect(storage.getValueType("builder")).toBe("builder");
|
|
112
|
+
});
|
|
113
|
+
|
|
114
|
+
test("detects FluentBuilder in nested object", () => {
|
|
115
|
+
const builder = createMockBuilder({ id: "test" });
|
|
116
|
+
const nested = {
|
|
117
|
+
level1: {
|
|
118
|
+
level2: {
|
|
119
|
+
builder,
|
|
120
|
+
},
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
|
|
124
|
+
storage.set("nested", nested as unknown as { deep: string });
|
|
125
|
+
|
|
126
|
+
expect(storage.getValueType("nested")).toBe("builder");
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
test("handles circular references without infinite loop", () => {
|
|
130
|
+
const obj: Record<string, unknown> = { value: "test" };
|
|
131
|
+
obj.circular = obj;
|
|
132
|
+
|
|
133
|
+
// Should not throw or hang
|
|
134
|
+
expect(() => {
|
|
135
|
+
storage.set("nested", obj as unknown as { deep: string });
|
|
136
|
+
}).not.toThrow();
|
|
137
|
+
});
|
|
138
|
+
|
|
139
|
+
test("returns false for plain objects", () => {
|
|
140
|
+
const plainObj = { a: 1, b: "string", c: [1, 2, 3] };
|
|
141
|
+
|
|
142
|
+
storage.set("nested", plainObj as unknown as { deep: string });
|
|
143
|
+
|
|
144
|
+
expect(storage.getValueType("nested")).toBe("static");
|
|
145
|
+
});
|
|
146
|
+
|
|
147
|
+
test("ignores objects with custom prototypes", () => {
|
|
148
|
+
class CustomClass {
|
|
149
|
+
value = "test";
|
|
150
|
+
}
|
|
151
|
+
const customInstance = new CustomClass();
|
|
152
|
+
|
|
153
|
+
// Objects with custom prototypes are treated as static
|
|
154
|
+
// (they won't be checked for nested builders)
|
|
155
|
+
storage.set("nested", customInstance as unknown as { deep: string });
|
|
156
|
+
|
|
157
|
+
expect(storage.getValueType("nested")).toBe("static");
|
|
158
|
+
});
|
|
159
|
+
});
|
|
160
|
+
|
|
161
|
+
describe("ValueStorage - Utility Methods", () => {
|
|
162
|
+
let storage: ValueStorage<TestType>;
|
|
163
|
+
|
|
164
|
+
beforeEach(() => {
|
|
165
|
+
storage = new ValueStorage<TestType>();
|
|
166
|
+
});
|
|
167
|
+
|
|
168
|
+
describe("has()", () => {
|
|
169
|
+
test("returns true for values in static storage", () => {
|
|
170
|
+
storage.set("value", "test");
|
|
171
|
+
expect(storage.has("value")).toBe(true);
|
|
172
|
+
});
|
|
173
|
+
|
|
174
|
+
test("returns true for values in builders storage", () => {
|
|
175
|
+
const builder = createMockBuilder({ id: "test" });
|
|
176
|
+
storage.set("builder", builder);
|
|
177
|
+
expect(storage.has("builder")).toBe(true);
|
|
178
|
+
});
|
|
179
|
+
|
|
180
|
+
test("returns true for values in mixedArrays storage", () => {
|
|
181
|
+
const builder = createMockBuilder({ id: "test" });
|
|
182
|
+
storage.set("mixedArray", [builder, "static"]);
|
|
183
|
+
expect(storage.has("mixedArray")).toBe(true);
|
|
184
|
+
});
|
|
185
|
+
|
|
186
|
+
test("returns false for unset values", () => {
|
|
187
|
+
expect(storage.has("value")).toBe(false);
|
|
188
|
+
});
|
|
189
|
+
});
|
|
190
|
+
|
|
191
|
+
describe("peek()", () => {
|
|
192
|
+
test("returns static value", () => {
|
|
193
|
+
storage.set("value", "hello");
|
|
194
|
+
expect(storage.peek("value")).toBe("hello");
|
|
195
|
+
});
|
|
196
|
+
|
|
197
|
+
test("returns mixed array", () => {
|
|
198
|
+
const builder = createMockBuilder({ id: "test" });
|
|
199
|
+
storage.set("mixedArray", [builder, "static"]);
|
|
200
|
+
expect(storage.peek("mixedArray")).toEqual([builder, "static"]);
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
test("returns undefined for builder", () => {
|
|
204
|
+
const builder = createMockBuilder({ id: "test" });
|
|
205
|
+
storage.set("builder", builder);
|
|
206
|
+
expect(storage.peek("builder")).toBeUndefined();
|
|
207
|
+
});
|
|
208
|
+
|
|
209
|
+
test("returns undefined for unset value", () => {
|
|
210
|
+
expect(storage.peek("value")).toBeUndefined();
|
|
211
|
+
});
|
|
212
|
+
});
|
|
213
|
+
|
|
214
|
+
describe("peekBuilder()", () => {
|
|
215
|
+
test("returns builder when exists", () => {
|
|
216
|
+
const builder = createMockBuilder({ id: "test" });
|
|
217
|
+
storage.set("builder", builder);
|
|
218
|
+
expect(storage.peekBuilder("builder")).toBe(builder);
|
|
219
|
+
});
|
|
220
|
+
|
|
221
|
+
test("returns undefined when no builder", () => {
|
|
222
|
+
storage.set("value", "static");
|
|
223
|
+
expect(storage.peekBuilder("value")).toBeUndefined();
|
|
224
|
+
});
|
|
225
|
+
|
|
226
|
+
test("returns undefined for unset value", () => {
|
|
227
|
+
expect(storage.peekBuilder("builder")).toBeUndefined();
|
|
228
|
+
});
|
|
229
|
+
|
|
230
|
+
test("returns undefined for object containing builder (not direct builder)", () => {
|
|
231
|
+
const builder = createMockBuilder({ id: "test" });
|
|
232
|
+
storage.set("nested", { wrapper: builder } as unknown as {
|
|
233
|
+
deep: string;
|
|
234
|
+
});
|
|
235
|
+
// peekBuilder returns the builder only if it's a direct FluentBuilder
|
|
236
|
+
// If it's an object containing a builder, peekBuilder returns undefined
|
|
237
|
+
expect(storage.peekBuilder("nested")).toBeUndefined();
|
|
238
|
+
});
|
|
239
|
+
});
|
|
240
|
+
|
|
241
|
+
describe("getValueType()", () => {
|
|
242
|
+
test("returns 'static' for static values", () => {
|
|
243
|
+
storage.set("value", "test");
|
|
244
|
+
expect(storage.getValueType("value")).toBe("static");
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
test("returns 'builder' for builders", () => {
|
|
248
|
+
const builder = createMockBuilder({ id: "test" });
|
|
249
|
+
storage.set("builder", builder);
|
|
250
|
+
expect(storage.getValueType("builder")).toBe("builder");
|
|
251
|
+
});
|
|
252
|
+
|
|
253
|
+
test("returns 'mixed-array' for mixed arrays", () => {
|
|
254
|
+
const builder = createMockBuilder({ id: "test" });
|
|
255
|
+
storage.set("mixedArray", [builder, "static"]);
|
|
256
|
+
expect(storage.getValueType("mixedArray")).toBe("mixed-array");
|
|
257
|
+
});
|
|
258
|
+
|
|
259
|
+
test("returns 'unset' for unset values", () => {
|
|
260
|
+
expect(storage.getValueType("value")).toBe("unset");
|
|
261
|
+
});
|
|
262
|
+
});
|
|
263
|
+
|
|
264
|
+
describe("unset()", () => {
|
|
265
|
+
test("removes from static storage", () => {
|
|
266
|
+
storage.set("value", "test");
|
|
267
|
+
storage.unset("value");
|
|
268
|
+
expect(storage.has("value")).toBe(false);
|
|
269
|
+
});
|
|
270
|
+
|
|
271
|
+
test("removes from builders storage", () => {
|
|
272
|
+
const builder = createMockBuilder({ id: "test" });
|
|
273
|
+
storage.set("builder", builder);
|
|
274
|
+
storage.unset("builder");
|
|
275
|
+
expect(storage.has("builder")).toBe(false);
|
|
276
|
+
});
|
|
277
|
+
|
|
278
|
+
test("removes from mixedArrays storage", () => {
|
|
279
|
+
const builder = createMockBuilder({ id: "test" });
|
|
280
|
+
storage.set("mixedArray", [builder, "static"]);
|
|
281
|
+
storage.unset("mixedArray");
|
|
282
|
+
expect(storage.has("mixedArray")).toBe(false);
|
|
283
|
+
});
|
|
284
|
+
|
|
285
|
+
test("removes from correct storage type", () => {
|
|
286
|
+
storage.set("value", "static");
|
|
287
|
+
storage.unset("value");
|
|
288
|
+
|
|
289
|
+
expect(storage.getValueType("value")).toBe("unset");
|
|
290
|
+
expect(storage.getValues().value).toBeUndefined();
|
|
291
|
+
expect(storage.getBuilders().has("value")).toBe(false);
|
|
292
|
+
expect(storage.getMixedArrays().has("value")).toBe(false);
|
|
293
|
+
});
|
|
294
|
+
});
|
|
295
|
+
});
|
|
296
|
+
|
|
297
|
+
describe("ValueStorage - clone()", () => {
|
|
298
|
+
let storage: ValueStorage<TestType>;
|
|
299
|
+
|
|
300
|
+
beforeEach(() => {
|
|
301
|
+
storage = new ValueStorage<TestType>();
|
|
302
|
+
});
|
|
303
|
+
|
|
304
|
+
test("creates independent copy of values", () => {
|
|
305
|
+
storage.set("value", "original");
|
|
306
|
+
storage.set("count", 42);
|
|
307
|
+
|
|
308
|
+
const cloned = storage.clone();
|
|
309
|
+
|
|
310
|
+
// Modify original
|
|
311
|
+
storage.set("value", "modified");
|
|
312
|
+
|
|
313
|
+
// Clone should be unchanged
|
|
314
|
+
expect(cloned.peek("value")).toBe("original");
|
|
315
|
+
expect(cloned.peek("count")).toBe(42);
|
|
316
|
+
});
|
|
317
|
+
|
|
318
|
+
test("creates new Sets for mixedArray indices", () => {
|
|
319
|
+
const builder = createMockBuilder({ id: "test" });
|
|
320
|
+
storage.set("mixedArray", [builder, "static"]);
|
|
321
|
+
|
|
322
|
+
const cloned = storage.clone();
|
|
323
|
+
|
|
324
|
+
// Get the mixed array metadata
|
|
325
|
+
const originalMeta = storage.getMixedArrays().get("mixedArray");
|
|
326
|
+
const clonedMeta = cloned.getMixedArrays().get("mixedArray");
|
|
327
|
+
|
|
328
|
+
// Should be different Set instances
|
|
329
|
+
expect(originalMeta?.builderIndices).not.toBe(clonedMeta?.builderIndices);
|
|
330
|
+
expect(originalMeta?.objectIndices).not.toBe(clonedMeta?.objectIndices);
|
|
331
|
+
|
|
332
|
+
// But should have same values
|
|
333
|
+
expect([...originalMeta!.builderIndices]).toEqual([
|
|
334
|
+
...clonedMeta!.builderIndices,
|
|
335
|
+
]);
|
|
336
|
+
});
|
|
337
|
+
|
|
338
|
+
test("modifications to clone do not affect original", () => {
|
|
339
|
+
storage.set("value", "original");
|
|
340
|
+
const builder = createMockBuilder({ id: "test" });
|
|
341
|
+
storage.set("builder", builder);
|
|
342
|
+
|
|
343
|
+
const cloned = storage.clone();
|
|
344
|
+
|
|
345
|
+
// Modify cloned
|
|
346
|
+
cloned.set("value", "cloned-value");
|
|
347
|
+
cloned.unset("builder");
|
|
348
|
+
|
|
349
|
+
// Original should be unchanged
|
|
350
|
+
expect(storage.peek("value")).toBe("original");
|
|
351
|
+
expect(storage.has("builder")).toBe(true);
|
|
352
|
+
|
|
353
|
+
// Clone should have new values
|
|
354
|
+
expect(cloned.peek("value")).toBe("cloned-value");
|
|
355
|
+
expect(cloned.has("builder")).toBe(false);
|
|
356
|
+
});
|
|
357
|
+
});
|
|
358
|
+
|
|
359
|
+
describe("ValueStorage - clear()", () => {
|
|
360
|
+
let storage: ValueStorage<TestType>;
|
|
361
|
+
|
|
362
|
+
beforeEach(() => {
|
|
363
|
+
storage = new ValueStorage<TestType>();
|
|
364
|
+
});
|
|
365
|
+
|
|
366
|
+
test("clears all storage types", () => {
|
|
367
|
+
const builder = createMockBuilder({ id: "test" });
|
|
368
|
+
|
|
369
|
+
storage.set("value", "static");
|
|
370
|
+
storage.set("builder", builder);
|
|
371
|
+
storage.set("mixedArray", [builder, "static"]);
|
|
372
|
+
|
|
373
|
+
storage.clear();
|
|
374
|
+
|
|
375
|
+
expect(storage.has("value")).toBe(false);
|
|
376
|
+
expect(storage.has("builder")).toBe(false);
|
|
377
|
+
expect(storage.has("mixedArray")).toBe(false);
|
|
378
|
+
|
|
379
|
+
expect(storage.getValues()).toEqual({});
|
|
380
|
+
expect(storage.getBuilders().size).toBe(0);
|
|
381
|
+
expect(storage.getMixedArrays().size).toBe(0);
|
|
382
|
+
});
|
|
383
|
+
});
|
|
384
|
+
|
|
385
|
+
describe("ValueStorage - Constructor", () => {
|
|
386
|
+
test("initializes with empty storage", () => {
|
|
387
|
+
const storage = new ValueStorage<TestType>();
|
|
388
|
+
|
|
389
|
+
expect(storage.getValues()).toEqual({});
|
|
390
|
+
expect(storage.getBuilders().size).toBe(0);
|
|
391
|
+
expect(storage.getMixedArrays().size).toBe(0);
|
|
392
|
+
});
|
|
393
|
+
|
|
394
|
+
test("accepts initial values", () => {
|
|
395
|
+
const initial: Partial<TestType> = {
|
|
396
|
+
value: "initial",
|
|
397
|
+
count: 10,
|
|
398
|
+
};
|
|
399
|
+
|
|
400
|
+
const storage = new ValueStorage<TestType>(initial);
|
|
401
|
+
|
|
402
|
+
expect(storage.peek("value")).toBe("initial");
|
|
403
|
+
expect(storage.peek("count")).toBe(10);
|
|
404
|
+
});
|
|
405
|
+
|
|
406
|
+
test("initial values are copied not referenced", () => {
|
|
407
|
+
const initial: Partial<TestType> = {
|
|
408
|
+
value: "initial",
|
|
409
|
+
};
|
|
410
|
+
|
|
411
|
+
const storage = new ValueStorage<TestType>(initial);
|
|
412
|
+
|
|
413
|
+
// Modify initial object
|
|
414
|
+
initial.value = "modified";
|
|
415
|
+
|
|
416
|
+
// Storage should be unchanged
|
|
417
|
+
expect(storage.peek("value")).toBe("initial");
|
|
418
|
+
});
|
|
419
|
+
});
|
|
420
|
+
|
|
421
|
+
describe("ValueStorage - Array Handling", () => {
|
|
422
|
+
let storage: ValueStorage<TestType>;
|
|
423
|
+
|
|
424
|
+
beforeEach(() => {
|
|
425
|
+
storage = new ValueStorage<TestType>();
|
|
426
|
+
});
|
|
427
|
+
|
|
428
|
+
test("static arrays go to values storage", () => {
|
|
429
|
+
storage.set("items", ["a", "b", "c"]);
|
|
430
|
+
|
|
431
|
+
expect(storage.getValueType("items")).toBe("static");
|
|
432
|
+
expect(storage.peek("items")).toEqual(["a", "b", "c"]);
|
|
433
|
+
});
|
|
434
|
+
|
|
435
|
+
test("arrays with only builders go to mixedArrays", () => {
|
|
436
|
+
const builder1 = createMockBuilder({ id: "1" });
|
|
437
|
+
const builder2 = createMockBuilder({ id: "2" });
|
|
438
|
+
|
|
439
|
+
storage.set("mixedArray", [builder1, builder2]);
|
|
440
|
+
|
|
441
|
+
expect(storage.getValueType("mixedArray")).toBe("mixed-array");
|
|
442
|
+
|
|
443
|
+
const meta = storage.getMixedArrays().get("mixedArray");
|
|
444
|
+
expect(meta?.builderIndices.has(0)).toBe(true);
|
|
445
|
+
expect(meta?.builderIndices.has(1)).toBe(true);
|
|
446
|
+
});
|
|
447
|
+
|
|
448
|
+
test("arrays with nested builders go to mixedArrays", () => {
|
|
449
|
+
const builder = createMockBuilder({ id: "test" });
|
|
450
|
+
const objWithBuilder = { wrapper: builder };
|
|
451
|
+
|
|
452
|
+
storage.set("mixedArray", [objWithBuilder, "static"]);
|
|
453
|
+
|
|
454
|
+
expect(storage.getValueType("mixedArray")).toBe("mixed-array");
|
|
455
|
+
|
|
456
|
+
const meta = storage.getMixedArrays().get("mixedArray");
|
|
457
|
+
expect(meta?.objectIndices.has(0)).toBe(true);
|
|
458
|
+
});
|
|
459
|
+
});
|