@evefrontier/dapp-kit 0.1.2 → 0.1.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/CHANGELOG.md +6 -0
- package/docs/api/assets/hierarchy.js +1 -1
- package/docs/api/assets/navigation.js +1 -1
- package/docs/api/assets/search.js +1 -1
- package/docs/api/classes/WalletNoAccountSelectedError.html +3 -3
- package/docs/api/classes/WalletNotConnectedError.html +3 -3
- package/docs/api/classes/WalletSponsoredTransactionNotSupportedError.html +3 -3
- package/docs/api/enums/ActionTypes.html +2 -2
- package/docs/api/enums/Assemblies.html +3 -2
- package/docs/api/enums/QueryParams.html +2 -2
- package/docs/api/enums/Severity.html +2 -2
- package/docs/api/enums/SponsoredTransactionActions.html +3 -3
- package/docs/api/enums/State.html +2 -2
- package/docs/api/enums/SupportedWallets.html +2 -2
- package/docs/api/enums/TYPEIDS.html +3 -3
- package/docs/api/functions/EveFrontierProvider.html +2 -2
- package/docs/api/functions/NotificationProvider.html +2 -2
- package/docs/api/functions/SmartObjectProvider.html +4 -4
- package/docs/api/functions/VaultProvider.html +1 -1
- package/docs/api/functions/abbreviateAddress.html +2 -2
- package/docs/api/functions/assertAssemblyType.html +2 -2
- package/docs/api/functions/clickToCopy.html +1 -1
- package/docs/api/functions/executeGraphQLQuery.html +2 -2
- package/docs/api/functions/findOwnerByAddress.html +2 -2
- package/docs/api/functions/formatDuration.html +2 -2
- package/docs/api/functions/formatM3.html +2 -2
- package/docs/api/functions/getAdjustedBurnRate.html +6 -0
- package/docs/api/functions/getAssemblyType.html +2 -2
- package/docs/api/functions/getAssemblyTypeApiString.html +2 -2
- package/docs/api/functions/getAssemblyWithOwner.html +3 -3
- package/docs/api/functions/getCharacterAndOwnedObjects.html +3 -0
- package/docs/api/functions/getCharacterOwnedObjects.html +5 -0
- package/docs/api/functions/getCharacterOwnedObjectsJson.html +5 -0
- package/docs/api/functions/getCharacterOwnerCapType.html +2 -2
- package/docs/api/functions/getCharacterPlayerProfileType.html +3 -0
- package/docs/api/functions/getCommonItems.html +1 -1
- package/docs/api/functions/getDappUrl.html +2 -2
- package/docs/api/functions/getDatahubGameInfo.html +2 -2
- package/docs/api/functions/getEnergyConfig.html +4 -0
- package/docs/api/functions/getEnergyConfigType.html +3 -0
- package/docs/api/functions/getEnergyUsageForType.html +5 -0
- package/docs/api/functions/getEnv.html +1 -1
- package/docs/api/functions/getEveWorldPackageId.html +2 -2
- package/docs/api/functions/getFuelEfficiencyConfig.html +4 -0
- package/docs/api/functions/getFuelEfficiencyConfigType.html +3 -0
- package/docs/api/functions/getFuelEfficiencyForType.html +5 -0
- package/docs/api/functions/getObjectByAddress.html +2 -2
- package/docs/api/functions/getObjectId.html +2 -2
- package/docs/api/functions/getObjectOwnerAndOwnedObjectsByType.html +2 -2
- package/docs/api/functions/getObjectOwnerAndOwnedObjectsWithJson.html +7 -0
- package/docs/api/functions/getObjectRegistryType.html +2 -2
- package/docs/api/functions/getObjectWithDynamicFields.html +2 -2
- package/docs/api/functions/getObjectWithJson.html +2 -2
- package/docs/api/functions/getObjectsByType.html +2 -2
- package/docs/api/functions/getOwnedObjectsByPackage.html +2 -2
- package/docs/api/functions/getOwnedObjectsByType.html +2 -2
- package/docs/api/functions/getRegistryAddress.html +2 -2
- package/docs/api/functions/getSingletonObjectByType.html +2 -2
- package/docs/api/functions/getSponsoredTransactionFeature.html +2 -2
- package/docs/api/functions/getSuiGraphqlEndpoint.html +2 -2
- package/docs/api/functions/getTxUrl.html +2 -2
- package/docs/api/functions/getVolumeM3.html +2 -2
- package/docs/api/functions/getWalletCharacters.html +3 -0
- package/docs/api/functions/hasSponsoredTransactionFeature.html +2 -2
- package/docs/api/functions/isOwner.html +3 -3
- package/docs/api/functions/parseCharacterFromJson.html +5 -0
- package/docs/api/functions/parseErrorFromMessage.html +1 -1
- package/docs/api/functions/parseStatus.html +2 -2
- package/docs/api/functions/parseURL.html +1 -1
- package/docs/api/functions/removeTrailingZeros.html +1 -1
- package/docs/api/functions/transformToAssembly.html +2 -2
- package/docs/api/functions/transformToCharacter.html +2 -2
- package/docs/api/functions/useConnection.html +2 -2
- package/docs/api/functions/useNotification.html +2 -2
- package/docs/api/functions/useSmartObject.html +2 -2
- package/docs/api/functions/useSponsoredTransaction.html +2 -2
- package/docs/api/functions/walletSupportsSponsoredTransaction.html +2 -2
- package/docs/api/hierarchy.html +1 -1
- package/docs/api/index.html +2 -2
- package/docs/api/interfaces/AddressOwner.html +2 -2
- package/docs/api/interfaces/AddressOwnerWithJson.html +2 -2
- package/docs/api/interfaces/AddressWithObjects.html +3 -0
- package/docs/api/interfaces/AdjustedBurnRate.html +8 -0
- package/docs/api/interfaces/AsMoveObjectRef.html +3 -0
- package/docs/api/interfaces/AssemblyProperties.html +6 -3
- package/docs/api/interfaces/CharacterAndOwnedObjectsNode.html +2 -0
- package/docs/api/interfaces/CharacterInfo.html +3 -3
- package/docs/api/interfaces/CharacterOwnerNode.html +3 -0
- package/docs/api/interfaces/ConfigExtractDynamicFieldNode.html +3 -0
- package/docs/api/interfaces/ContentsBcs.html +2 -0
- package/docs/api/interfaces/ContentsJsonAndBcs.html +3 -0
- package/docs/api/interfaces/ContentsJsonOnly.html +3 -0
- package/docs/api/interfaces/ContentsTypeAndBcs.html +3 -0
- package/docs/api/interfaces/ContentsTypeJsonAndBcs.html +4 -0
- package/docs/api/interfaces/DatahubGameInfo.html +14 -16
- package/docs/api/interfaces/DetailedAssemblyResponse.html +6 -3
- package/docs/api/interfaces/DetailedSmartCharacterResponse.html +2 -2
- package/docs/api/interfaces/DynamicFieldNode.html +2 -2
- package/docs/api/interfaces/EphemeralInventory.html +2 -2
- package/docs/api/interfaces/EveFrontierSponsoredTransactionFeature.html +3 -3
- package/docs/api/interfaces/ExtractAsMoveObjectNode.html +4 -0
- package/docs/api/interfaces/GateModule.html +2 -5
- package/docs/api/interfaces/GetCharacterAndOwnedObjectsResponse.html +2 -0
- package/docs/api/interfaces/GetObjectAndCharacterOwnerResponse.html +2 -0
- package/docs/api/interfaces/GetObjectByAddressResponse.html +2 -2
- package/docs/api/interfaces/GetObjectOwnerAndOwnedObjectsResponse.html +2 -2
- package/docs/api/interfaces/GetObjectOwnerAndOwnedObjectsWithJsonResponse.html +2 -2
- package/docs/api/interfaces/GetObjectResponse.html +2 -2
- package/docs/api/interfaces/GetObjectWithJsonResponse.html +2 -2
- package/docs/api/interfaces/GetObjectsByTypeResponse.html +2 -2
- package/docs/api/interfaces/GetOwnedObjectsByPackageResponse.html +2 -2
- package/docs/api/interfaces/GetOwnedObjectsByTypeResponse.html +2 -2
- package/docs/api/interfaces/GetSingletonConfigObjectByTypeResponse.html +2 -0
- package/docs/api/interfaces/GetSingletonObjectByTypeResponse.html +2 -2
- package/docs/api/interfaces/GetWalletCharactersResponse.html +2 -0
- package/docs/api/interfaces/GraphQLResponse.html +2 -2
- package/docs/api/interfaces/InventoryItem.html +2 -2
- package/docs/api/interfaces/ManufacturingModule.html +1 -2
- package/docs/api/interfaces/MoveObjectContents.html +2 -2
- package/docs/api/interfaces/MoveObjectData.html +2 -2
- package/docs/api/interfaces/NetworkNodeModule.html +2 -2
- package/docs/api/interfaces/NotificationContextType.html +2 -2
- package/docs/api/interfaces/NotificationState.html +2 -2
- package/docs/api/interfaces/Notify.html +2 -2
- package/docs/api/interfaces/ObjectNodes.html +2 -0
- package/docs/api/interfaces/ObjectWithContentsNode.html +2 -2
- package/docs/api/interfaces/OwnedObjectAddressNode.html +2 -2
- package/docs/api/interfaces/OwnedObjectFullNode.html +2 -2
- package/docs/api/interfaces/OwnedObjectNode.html +2 -2
- package/docs/api/interfaces/OwnedObjectNodeWithJson.html +2 -2
- package/docs/api/interfaces/OwnerCapData.html +3 -3
- package/docs/api/interfaces/PageInfo.html +2 -2
- package/docs/api/interfaces/PreviousTransaction.html +2 -0
- package/docs/api/interfaces/RawCharacterData.html +3 -3
- package/docs/api/interfaces/RawSuiObjectData.html +8 -8
- package/docs/api/interfaces/RefineryModule.html +1 -2
- package/docs/api/interfaces/SmartAssemblyResponse.html +6 -2
- package/docs/api/interfaces/SmartObjectContextType.html +4 -4
- package/docs/api/interfaces/SponsoredTransactionInput.html +6 -6
- package/docs/api/interfaces/SponsoredTransactionOutput.html +5 -5
- package/docs/api/interfaces/StorageModule.html +2 -3
- package/docs/api/interfaces/SuiObjectResponse.html +2 -2
- package/docs/api/interfaces/TransformOptions.html +7 -5
- package/docs/api/interfaces/TurretModule.html +1 -2
- package/docs/api/interfaces/TypeRepr.html +2 -0
- package/docs/api/interfaces/TypeReprWithLayout.html +3 -0
- package/docs/api/interfaces/VaultContextType.html +3 -3
- package/docs/api/modules.html +1 -1
- package/docs/api/types/AssemblyType.html +1 -1
- package/docs/api/types/ContentsTypeAndJson.html +2 -0
- package/docs/api/types/ErrorType.html +2 -2
- package/docs/api/types/MoveObjectRefWithJson.html +2 -0
- package/docs/api/types/SendSponsoredTransactionFn.html +1 -1
- package/docs/api/types/SponsoredTransactionArgs.html +2 -2
- package/docs/api/types/SponsoredTransactionAssemblyType.html +2 -2
- package/docs/api/types/SponsoredTransactionMethod.html +2 -2
- package/docs/api/types/UseSponsoredTransactionArgs.html +2 -2
- package/docs/api/types/UseSponsoredTransactionError.html +2 -2
- package/docs/api/types/UseSponsoredTransactionMutationOptions.html +2 -2
- package/docs/api/variables/ASSEMBLY_TYPE_API_STRING.html +2 -2
- package/docs/api/variables/ERRORS.html +1 -1
- package/docs/api/variables/ERROR_MESSAGES.html +1 -1
- package/docs/api/variables/EVEFRONTIER_SPONSORED_TRANSACTION.html +2 -2
- package/docs/api/variables/EXCLUDED_TYPEIDS.html +1 -1
- package/docs/api/variables/GET_OBJECTS_BY_TYPE.html +2 -2
- package/docs/api/variables/GET_OBJECT_BY_ADDRESS.html +2 -2
- package/docs/api/variables/GET_OBJECT_OWNER_AND_OWNED_OBJECTS_BY_TYPE.html +2 -2
- package/docs/api/variables/GET_OBJECT_OWNER_AND_OWNED_OBJECTS_WITH_JSON.html +2 -2
- package/docs/api/variables/GET_OBJECT_WITH_DYNAMIC_FIELDS.html +2 -2
- package/docs/api/variables/GET_OBJECT_WITH_JSON.html +2 -2
- package/docs/api/variables/GET_OWNED_OBJECTS_BY_PACKAGE.html +2 -2
- package/docs/api/variables/GET_OWNED_OBJECTS_BY_TYPE.html +2 -2
- package/docs/api/variables/GET_SINGLETON_CONFIG_OBJECT_BY_TYPE.html +2 -0
- package/docs/api/variables/GET_SINGLETON_OBJECT_BY_TYPE.html +2 -2
- package/docs/api/variables/GET_WALLET_CHARACTERS.html +3 -0
- package/docs/api/variables/NotificationContext.html +1 -1
- package/docs/api/variables/ONE_M3.html +2 -2
- package/docs/api/variables/POLLING_INTERVAL.html +2 -2
- package/docs/api/variables/STORAGE_KEYS.html +2 -2
- package/docs/api/variables/SmartObjectContext.html +1 -1
- package/docs/api/variables/VaultContext.html +1 -1
- package/docs/api/variables/dAppKit.html +2 -2
- package/graphql/client.ts +96 -24
- package/graphql/index.ts +5 -39
- package/graphql/queries.ts +157 -1
- package/graphql/types.ts +188 -155
- package/hooks/index.ts +1 -0
- package/index.ts +7 -71
- package/package.json +1 -1
- package/providers/SmartObjectProvider.tsx +17 -17
- package/types/contexts.ts +1 -1
- package/types/sponsoredTransaction.ts +1 -0
- package/types/types.ts +52 -10
- package/types/worldApiReturnTypes.ts +13 -37
- package/utils/__tests__/burnRate.test.ts +121 -0
- package/utils/__tests__/character.test.ts +112 -0
- package/utils/__tests__/characterOwnedObjects.test.ts +222 -0
- package/utils/__tests__/config.test.ts +424 -0
- package/utils/__tests__/mapping.test.ts +2 -2
- package/utils/burnRate.ts +46 -0
- package/utils/character.ts +46 -0
- package/utils/characterOwnedObjects.ts +52 -0
- package/utils/config.ts +151 -0
- package/utils/constants.ts +39 -5
- package/utils/datahub.ts +3 -41
- package/utils/errors.ts +1 -1
- package/utils/index.ts +10 -1
- package/utils/mapping.ts +5 -2
- package/utils/transforms.ts +27 -31
- package/wallet/features.ts +5 -0
- package/docs/api/interfaces/GameTypeResponse.html +0 -13
|
@@ -0,0 +1,222 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
import type { GetCharacterAndOwnedObjectsResponse } from "../../graphql/types";
|
|
3
|
+
import {
|
|
4
|
+
getCharacterOwnedObjectsJson,
|
|
5
|
+
getCharacterOwnedObjects,
|
|
6
|
+
} from "../characterOwnedObjects";
|
|
7
|
+
|
|
8
|
+
vi.mock("../../graphql/client", () => ({
|
|
9
|
+
getCharacterAndOwnedObjects: vi.fn(),
|
|
10
|
+
}));
|
|
11
|
+
|
|
12
|
+
import { getCharacterAndOwnedObjects } from "../../graphql/client";
|
|
13
|
+
|
|
14
|
+
/** Minimal owned object node shape (Gate/Assembly/Character/NetworkNode). */
|
|
15
|
+
function ownedObjectNode(json: Record<string, unknown>, typeRepr: string) {
|
|
16
|
+
return {
|
|
17
|
+
contents: {
|
|
18
|
+
extract: {
|
|
19
|
+
asAddress: {
|
|
20
|
+
asObject: {
|
|
21
|
+
asMoveObject: {
|
|
22
|
+
contents: { type: { repr: typeRepr }, json },
|
|
23
|
+
},
|
|
24
|
+
},
|
|
25
|
+
},
|
|
26
|
+
},
|
|
27
|
+
},
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/** Builds a valid GetCharacterAndOwnedObjectsResponse with the given owned-object json payloads. */
|
|
32
|
+
function buildResponse(
|
|
33
|
+
ownedJsons: Record<string, unknown>[],
|
|
34
|
+
typeRepr = "0x2::example::Object",
|
|
35
|
+
): GetCharacterAndOwnedObjectsResponse {
|
|
36
|
+
const nodes = ownedJsons.map((json) => ownedObjectNode(json, typeRepr));
|
|
37
|
+
return {
|
|
38
|
+
address: {
|
|
39
|
+
address: "0xwallet",
|
|
40
|
+
objects: {
|
|
41
|
+
nodes: [
|
|
42
|
+
{
|
|
43
|
+
contents: {
|
|
44
|
+
extract: {
|
|
45
|
+
asAddress: {
|
|
46
|
+
asObject: {
|
|
47
|
+
asMoveObject: {
|
|
48
|
+
contents: {
|
|
49
|
+
type: { repr: "0x2::character::Character" },
|
|
50
|
+
json: { id: "0xchar", metadata: {} },
|
|
51
|
+
},
|
|
52
|
+
},
|
|
53
|
+
},
|
|
54
|
+
objects: { nodes },
|
|
55
|
+
},
|
|
56
|
+
},
|
|
57
|
+
},
|
|
58
|
+
},
|
|
59
|
+
],
|
|
60
|
+
},
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
describe("getCharacterOwnedObjectsJson", () => {
|
|
66
|
+
it("returns undefined when data is undefined", () => {
|
|
67
|
+
expect(getCharacterOwnedObjectsJson(undefined)).toBeUndefined();
|
|
68
|
+
});
|
|
69
|
+
|
|
70
|
+
it("returns undefined when data is null", () => {
|
|
71
|
+
expect(getCharacterOwnedObjectsJson(null as unknown as undefined)).toBeUndefined();
|
|
72
|
+
});
|
|
73
|
+
|
|
74
|
+
it("returns undefined when address is missing", () => {
|
|
75
|
+
expect(
|
|
76
|
+
getCharacterOwnedObjectsJson(
|
|
77
|
+
{} as unknown as GetCharacterAndOwnedObjectsResponse,
|
|
78
|
+
),
|
|
79
|
+
).toBeUndefined();
|
|
80
|
+
});
|
|
81
|
+
|
|
82
|
+
it("returns undefined when address.objects is missing", () => {
|
|
83
|
+
expect(
|
|
84
|
+
getCharacterOwnedObjectsJson({
|
|
85
|
+
address: { address: "0x" },
|
|
86
|
+
} as unknown as GetCharacterAndOwnedObjectsResponse),
|
|
87
|
+
).toBeUndefined();
|
|
88
|
+
});
|
|
89
|
+
|
|
90
|
+
it("returns undefined when address.objects.nodes is missing", () => {
|
|
91
|
+
expect(
|
|
92
|
+
getCharacterOwnedObjectsJson({
|
|
93
|
+
address: { address: "0x", objects: {} },
|
|
94
|
+
} as unknown as GetCharacterAndOwnedObjectsResponse),
|
|
95
|
+
).toBeUndefined();
|
|
96
|
+
});
|
|
97
|
+
|
|
98
|
+
it("returns undefined when address.objects.nodes is empty", () => {
|
|
99
|
+
expect(
|
|
100
|
+
getCharacterOwnedObjectsJson({
|
|
101
|
+
address: { address: "0x", objects: { nodes: [] } },
|
|
102
|
+
}),
|
|
103
|
+
).toBeUndefined();
|
|
104
|
+
});
|
|
105
|
+
|
|
106
|
+
it("returns undefined when first node has no contents.extract.asAddress.objects", () => {
|
|
107
|
+
const data = {
|
|
108
|
+
address: {
|
|
109
|
+
address: "0x",
|
|
110
|
+
objects: {
|
|
111
|
+
nodes: [
|
|
112
|
+
{
|
|
113
|
+
contents: {
|
|
114
|
+
extract: {
|
|
115
|
+
asAddress: {
|
|
116
|
+
/* objects missing – path ends here */
|
|
117
|
+
},
|
|
118
|
+
},
|
|
119
|
+
},
|
|
120
|
+
},
|
|
121
|
+
],
|
|
122
|
+
},
|
|
123
|
+
},
|
|
124
|
+
} as unknown as GetCharacterAndOwnedObjectsResponse;
|
|
125
|
+
expect(getCharacterOwnedObjectsJson(data)).toBeUndefined();
|
|
126
|
+
});
|
|
127
|
+
|
|
128
|
+
it("returns undefined when objects.nodes is empty", () => {
|
|
129
|
+
const data = buildResponse([]);
|
|
130
|
+
expect(getCharacterOwnedObjectsJson(data)).toBeUndefined();
|
|
131
|
+
});
|
|
132
|
+
|
|
133
|
+
it("extracts json array from full deep GraphQL shape (single object)", () => {
|
|
134
|
+
const json1 = { id: "0xobj1", type_id: "123", owner_cap_id: "0xcap" };
|
|
135
|
+
const data = buildResponse([json1]);
|
|
136
|
+
const result = getCharacterOwnedObjectsJson(data);
|
|
137
|
+
expect(result).toEqual([json1]);
|
|
138
|
+
});
|
|
139
|
+
|
|
140
|
+
it("extracts json array from full deep GraphQL shape (multiple objects)", () => {
|
|
141
|
+
const json1 = { id: "0xgate", type_id: "gate" };
|
|
142
|
+
const json2 = { id: "0xasm", type_id: "assembly", metadata: {} };
|
|
143
|
+
const json3 = { id: "0xchar", tribe_id: 17, character_address: "0xaddr" };
|
|
144
|
+
const data = buildResponse([json1, json2, json3]);
|
|
145
|
+
const result = getCharacterOwnedObjectsJson(data);
|
|
146
|
+
expect(result).toEqual([json1, json2, json3]);
|
|
147
|
+
});
|
|
148
|
+
|
|
149
|
+
it("preserves exact json payloads (regression: deep path must match GraphQL shape)", () => {
|
|
150
|
+
const nested = {
|
|
151
|
+
id: "0xnode",
|
|
152
|
+
fuel: { quantity: "100", is_burning: false },
|
|
153
|
+
connected_assembly_ids: ["0xa", "0xb"],
|
|
154
|
+
};
|
|
155
|
+
const data = buildResponse([nested]);
|
|
156
|
+
const result = getCharacterOwnedObjectsJson(data);
|
|
157
|
+
expect(result).toHaveLength(1);
|
|
158
|
+
expect(result![0]).toEqual(nested);
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
describe("getCharacterOwnedObjects", () => {
|
|
163
|
+
beforeEach(() => {
|
|
164
|
+
vi.clearAllMocks();
|
|
165
|
+
});
|
|
166
|
+
|
|
167
|
+
afterEach(() => {
|
|
168
|
+
vi.restoreAllMocks();
|
|
169
|
+
});
|
|
170
|
+
|
|
171
|
+
it("returns extracted json array when response has data with owned objects", async () => {
|
|
172
|
+
const payloads = [
|
|
173
|
+
{ id: "0x1", type_id: "gate" },
|
|
174
|
+
{ id: "0x2", type_id: "assembly" },
|
|
175
|
+
];
|
|
176
|
+
const responseData = buildResponse(payloads);
|
|
177
|
+
vi.mocked(getCharacterAndOwnedObjects).mockResolvedValue({
|
|
178
|
+
data: responseData,
|
|
179
|
+
});
|
|
180
|
+
|
|
181
|
+
const result = await getCharacterOwnedObjects("0xwallet");
|
|
182
|
+
|
|
183
|
+
expect(getCharacterAndOwnedObjects).toHaveBeenCalledWith("0xwallet");
|
|
184
|
+
expect(result).toEqual(payloads);
|
|
185
|
+
});
|
|
186
|
+
|
|
187
|
+
it("returns undefined when response.data is undefined", async () => {
|
|
188
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
189
|
+
vi.mocked(getCharacterAndOwnedObjects).mockResolvedValue({});
|
|
190
|
+
|
|
191
|
+
const result = await getCharacterOwnedObjects("0xwallet");
|
|
192
|
+
|
|
193
|
+
expect(result).toBeUndefined();
|
|
194
|
+
expect(warnSpy).toHaveBeenCalledWith(
|
|
195
|
+
"[Dapp] No data returned from getCharacterAndOwnedObjects",
|
|
196
|
+
);
|
|
197
|
+
warnSpy.mockRestore();
|
|
198
|
+
});
|
|
199
|
+
|
|
200
|
+
it("returns undefined when response.data has no owned objects (empty nodes)", async () => {
|
|
201
|
+
const responseData = buildResponse([]);
|
|
202
|
+
vi.mocked(getCharacterAndOwnedObjects).mockResolvedValue({
|
|
203
|
+
data: responseData,
|
|
204
|
+
});
|
|
205
|
+
|
|
206
|
+
const result = await getCharacterOwnedObjects("0xwallet");
|
|
207
|
+
|
|
208
|
+
expect(result).toBeUndefined();
|
|
209
|
+
});
|
|
210
|
+
|
|
211
|
+
it("returns undefined when response.data.address is missing", async () => {
|
|
212
|
+
const warnSpy = vi.spyOn(console, "warn").mockImplementation(() => {});
|
|
213
|
+
vi.mocked(getCharacterAndOwnedObjects).mockResolvedValue({
|
|
214
|
+
data: {} as GetCharacterAndOwnedObjectsResponse,
|
|
215
|
+
});
|
|
216
|
+
|
|
217
|
+
const result = await getCharacterOwnedObjects("0xwallet");
|
|
218
|
+
|
|
219
|
+
expect(result).toBeUndefined();
|
|
220
|
+
warnSpy.mockRestore();
|
|
221
|
+
});
|
|
222
|
+
});
|
|
@@ -0,0 +1,424 @@
|
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, it, vi } from "vitest";
|
|
2
|
+
|
|
3
|
+
const TEST_EVE_WORLD_PACKAGE_ID =
|
|
4
|
+
"0x2ff3e06b96eb830bdcffbc6cae9b8fe43f005c3b94cef05d9ec23057df16f107";
|
|
5
|
+
|
|
6
|
+
vi.mock("../../graphql/client", () => ({
|
|
7
|
+
getSingletonConfigObjectByType: vi.fn(),
|
|
8
|
+
}));
|
|
9
|
+
|
|
10
|
+
import { getSingletonConfigObjectByType } from "../../graphql/client";
|
|
11
|
+
import {
|
|
12
|
+
getEnergyConfig,
|
|
13
|
+
getEnergyUsageForType,
|
|
14
|
+
getFuelEfficiencyConfig,
|
|
15
|
+
getFuelEfficiencyForType,
|
|
16
|
+
resetConfigCachesForTesting,
|
|
17
|
+
} from "../config";
|
|
18
|
+
import { getEnergyConfigType, getFuelEfficiencyConfigType } from "../constants";
|
|
19
|
+
|
|
20
|
+
/** Builds the response shape that config reads: data.objects.nodes[0].asMoveObject.contents.extract...dynamicFields.nodes */
|
|
21
|
+
function mockConfigResponse(
|
|
22
|
+
nodes: Array<{ key: { json: string }; value: { json: string } }>,
|
|
23
|
+
) {
|
|
24
|
+
return {
|
|
25
|
+
data: {
|
|
26
|
+
objects: {
|
|
27
|
+
nodes: [
|
|
28
|
+
{
|
|
29
|
+
address: "0xconfig",
|
|
30
|
+
asMoveObject: {
|
|
31
|
+
contents: {
|
|
32
|
+
extract: {
|
|
33
|
+
extract: {
|
|
34
|
+
asAddress: {
|
|
35
|
+
addressAt: {
|
|
36
|
+
dynamicFields: {
|
|
37
|
+
pageInfo: { hasNextPage: false, endCursor: null },
|
|
38
|
+
nodes,
|
|
39
|
+
},
|
|
40
|
+
},
|
|
41
|
+
},
|
|
42
|
+
},
|
|
43
|
+
},
|
|
44
|
+
},
|
|
45
|
+
},
|
|
46
|
+
},
|
|
47
|
+
],
|
|
48
|
+
},
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
describe("config utilities", () => {
|
|
54
|
+
beforeEach(() => {
|
|
55
|
+
vi.clearAllMocks();
|
|
56
|
+
vi.stubEnv("VITE_EVE_WORLD_PACKAGE_ID", TEST_EVE_WORLD_PACKAGE_ID);
|
|
57
|
+
resetConfigCachesForTesting();
|
|
58
|
+
});
|
|
59
|
+
|
|
60
|
+
afterEach(() => {
|
|
61
|
+
vi.unstubAllEnvs();
|
|
62
|
+
});
|
|
63
|
+
|
|
64
|
+
// ============================================================================
|
|
65
|
+
// Parsing (via getEnergyConfig with mocked response)
|
|
66
|
+
// ============================================================================
|
|
67
|
+
describe("parsing", () => {
|
|
68
|
+
it("returns empty object when nodes is undefined (e.g. missing path)", async () => {
|
|
69
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue({
|
|
70
|
+
data: {
|
|
71
|
+
objects: {
|
|
72
|
+
nodes: [
|
|
73
|
+
{
|
|
74
|
+
address: "0xcfg",
|
|
75
|
+
asMoveObject: {
|
|
76
|
+
contents: {
|
|
77
|
+
extract: {
|
|
78
|
+
extract: {
|
|
79
|
+
asAddress: {
|
|
80
|
+
addressAt: {
|
|
81
|
+
dynamicFields: { nodes: undefined },
|
|
82
|
+
},
|
|
83
|
+
},
|
|
84
|
+
},
|
|
85
|
+
},
|
|
86
|
+
},
|
|
87
|
+
},
|
|
88
|
+
},
|
|
89
|
+
],
|
|
90
|
+
},
|
|
91
|
+
},
|
|
92
|
+
} as unknown as Awaited<
|
|
93
|
+
ReturnType<typeof getSingletonConfigObjectByType>
|
|
94
|
+
>);
|
|
95
|
+
|
|
96
|
+
const result = await getEnergyConfig();
|
|
97
|
+
|
|
98
|
+
expect(result).toEqual({});
|
|
99
|
+
});
|
|
100
|
+
|
|
101
|
+
it("returns empty object when nodes is empty array", async () => {
|
|
102
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
103
|
+
mockConfigResponse([]),
|
|
104
|
+
);
|
|
105
|
+
|
|
106
|
+
const result = await getEnergyConfig();
|
|
107
|
+
|
|
108
|
+
expect(result).toEqual({});
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
it("parses valid key/value nodes into typeId -> value map", async () => {
|
|
112
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
113
|
+
mockConfigResponse([
|
|
114
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
115
|
+
{ key: { json: "88067" }, value: { json: "100" } },
|
|
116
|
+
{ key: { json: "92279" }, value: { json: "10" } },
|
|
117
|
+
]),
|
|
118
|
+
);
|
|
119
|
+
|
|
120
|
+
const result = await getEnergyConfig();
|
|
121
|
+
|
|
122
|
+
expect(result).toEqual({
|
|
123
|
+
77917: 500,
|
|
124
|
+
88067: 100,
|
|
125
|
+
92279: 10,
|
|
126
|
+
});
|
|
127
|
+
});
|
|
128
|
+
|
|
129
|
+
it("skips entries with invalid (non-numeric) key and continues parsing", async () => {
|
|
130
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
131
|
+
mockConfigResponse([
|
|
132
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
133
|
+
{ key: { json: "not-a-number" }, value: { json: "99" } },
|
|
134
|
+
{ key: { json: "88067" }, value: { json: "100" } },
|
|
135
|
+
]),
|
|
136
|
+
);
|
|
137
|
+
|
|
138
|
+
const result = await getEnergyConfig();
|
|
139
|
+
|
|
140
|
+
expect(result).toEqual({
|
|
141
|
+
77917: 500,
|
|
142
|
+
88067: 100,
|
|
143
|
+
});
|
|
144
|
+
});
|
|
145
|
+
|
|
146
|
+
it("uses 0 for entries with invalid (non-numeric) value", async () => {
|
|
147
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
148
|
+
mockConfigResponse([
|
|
149
|
+
{ key: { json: "77917" }, value: { json: "nope" } },
|
|
150
|
+
{ key: { json: "88067" }, value: { json: "100" } },
|
|
151
|
+
]),
|
|
152
|
+
);
|
|
153
|
+
|
|
154
|
+
const result = await getEnergyConfig();
|
|
155
|
+
|
|
156
|
+
expect(result).toEqual({
|
|
157
|
+
77917: 0,
|
|
158
|
+
88067: 100,
|
|
159
|
+
});
|
|
160
|
+
});
|
|
161
|
+
|
|
162
|
+
it("does not silently zero out known type IDs when data is present", async () => {
|
|
163
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
164
|
+
mockConfigResponse([
|
|
165
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
166
|
+
{ key: { json: "88071" }, value: { json: "300" } },
|
|
167
|
+
]),
|
|
168
|
+
);
|
|
169
|
+
|
|
170
|
+
const config = await getEnergyConfig();
|
|
171
|
+
const usage77917 = await getEnergyUsageForType(77917);
|
|
172
|
+
const usage88071 = await getEnergyUsageForType(88071);
|
|
173
|
+
const usageMissing = await getEnergyUsageForType(99999);
|
|
174
|
+
|
|
175
|
+
expect(config[77917]).toBe(500);
|
|
176
|
+
expect(config[88071]).toBe(300);
|
|
177
|
+
expect(usage77917).toBe(500);
|
|
178
|
+
expect(usage88071).toBe(300);
|
|
179
|
+
expect(usageMissing).toBe(0);
|
|
180
|
+
});
|
|
181
|
+
});
|
|
182
|
+
|
|
183
|
+
// ============================================================================
|
|
184
|
+
// Caching
|
|
185
|
+
// ============================================================================
|
|
186
|
+
describe("caching", () => {
|
|
187
|
+
it("calls GraphQL once and returns same reference on second getEnergyConfig()", async () => {
|
|
188
|
+
const nodes = [{ key: { json: "77917" }, value: { json: "500" } }];
|
|
189
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
190
|
+
mockConfigResponse(nodes),
|
|
191
|
+
);
|
|
192
|
+
|
|
193
|
+
const first = await getEnergyConfig();
|
|
194
|
+
const second = await getEnergyConfig();
|
|
195
|
+
|
|
196
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledTimes(1);
|
|
197
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledWith(
|
|
198
|
+
getEnergyConfigType(),
|
|
199
|
+
"assembly_energy",
|
|
200
|
+
);
|
|
201
|
+
expect(first).toBe(second);
|
|
202
|
+
expect(second).toEqual({ 77917: 500 });
|
|
203
|
+
});
|
|
204
|
+
|
|
205
|
+
it("calls GraphQL once for getFuelEfficiencyConfig and returns same reference on second call", async () => {
|
|
206
|
+
const nodes = [{ key: { json: "12345" }, value: { json: "75" } }];
|
|
207
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue({
|
|
208
|
+
data: {
|
|
209
|
+
objects: {
|
|
210
|
+
nodes: [
|
|
211
|
+
{
|
|
212
|
+
address: "0xfuel",
|
|
213
|
+
asMoveObject: {
|
|
214
|
+
contents: {
|
|
215
|
+
extract: {
|
|
216
|
+
extract: {
|
|
217
|
+
asAddress: {
|
|
218
|
+
addressAt: {
|
|
219
|
+
dynamicFields: {
|
|
220
|
+
pageInfo: { hasNextPage: false },
|
|
221
|
+
nodes,
|
|
222
|
+
},
|
|
223
|
+
},
|
|
224
|
+
},
|
|
225
|
+
},
|
|
226
|
+
},
|
|
227
|
+
},
|
|
228
|
+
},
|
|
229
|
+
},
|
|
230
|
+
],
|
|
231
|
+
},
|
|
232
|
+
},
|
|
233
|
+
} as Awaited<ReturnType<typeof getSingletonConfigObjectByType>>);
|
|
234
|
+
|
|
235
|
+
const first = await getFuelEfficiencyConfig();
|
|
236
|
+
const second = await getFuelEfficiencyConfig();
|
|
237
|
+
|
|
238
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledWith(
|
|
239
|
+
getFuelEfficiencyConfigType(),
|
|
240
|
+
"fuel_efficiency",
|
|
241
|
+
);
|
|
242
|
+
expect(first).toBe(second);
|
|
243
|
+
expect(second).toEqual({ 12345: 75 });
|
|
244
|
+
});
|
|
245
|
+
});
|
|
246
|
+
|
|
247
|
+
// ============================================================================
|
|
248
|
+
// Concurrent-call deduplication
|
|
249
|
+
// ============================================================================
|
|
250
|
+
describe("concurrent-call deduplication", () => {
|
|
251
|
+
it("performs a single fetch when getEnergyConfig() is invoked concurrently", async () => {
|
|
252
|
+
type MockResponse = Awaited<
|
|
253
|
+
ReturnType<typeof getSingletonConfigObjectByType>
|
|
254
|
+
>;
|
|
255
|
+
let resolveFetch: (v: MockResponse) => void;
|
|
256
|
+
const fetchPromise = new Promise<MockResponse>((r) => {
|
|
257
|
+
resolveFetch = r;
|
|
258
|
+
});
|
|
259
|
+
vi.mocked(getSingletonConfigObjectByType).mockReturnValue(fetchPromise);
|
|
260
|
+
|
|
261
|
+
const concurrentCalls = [
|
|
262
|
+
getEnergyConfig(),
|
|
263
|
+
getEnergyConfig(),
|
|
264
|
+
getEnergyConfig(),
|
|
265
|
+
getEnergyConfig(),
|
|
266
|
+
getEnergyConfig(),
|
|
267
|
+
];
|
|
268
|
+
|
|
269
|
+
resolveFetch!(
|
|
270
|
+
mockConfigResponse([
|
|
271
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
272
|
+
]),
|
|
273
|
+
);
|
|
274
|
+
|
|
275
|
+
const results = await Promise.all(concurrentCalls);
|
|
276
|
+
|
|
277
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledTimes(1);
|
|
278
|
+
expect(results.every((r) => r === results[0])).toBe(true);
|
|
279
|
+
expect(results[0]).toEqual({ 77917: 500 });
|
|
280
|
+
});
|
|
281
|
+
|
|
282
|
+
it("performs a single fetch when getFuelEfficiencyConfig() is invoked concurrently", async () => {
|
|
283
|
+
type MockResponse = Awaited<
|
|
284
|
+
ReturnType<typeof getSingletonConfigObjectByType>
|
|
285
|
+
>;
|
|
286
|
+
let resolveFetch: (v: MockResponse) => void;
|
|
287
|
+
const fetchPromise = new Promise<MockResponse>((r) => {
|
|
288
|
+
resolveFetch = r;
|
|
289
|
+
});
|
|
290
|
+
vi.mocked(getSingletonConfigObjectByType).mockReturnValue(fetchPromise);
|
|
291
|
+
|
|
292
|
+
const concurrentCalls = [
|
|
293
|
+
getFuelEfficiencyConfig(),
|
|
294
|
+
getFuelEfficiencyConfig(),
|
|
295
|
+
getFuelEfficiencyConfig(),
|
|
296
|
+
];
|
|
297
|
+
|
|
298
|
+
resolveFetch!(
|
|
299
|
+
mockConfigResponse([
|
|
300
|
+
{ key: { json: "88071" }, value: { json: "200" } },
|
|
301
|
+
]),
|
|
302
|
+
);
|
|
303
|
+
|
|
304
|
+
const results = await Promise.all(concurrentCalls);
|
|
305
|
+
|
|
306
|
+
expect(getSingletonConfigObjectByType).toHaveBeenCalledTimes(1);
|
|
307
|
+
expect(results.every((r) => r === results[0])).toBe(true);
|
|
308
|
+
expect(results[0]).toEqual({ 88071: 200 });
|
|
309
|
+
});
|
|
310
|
+
});
|
|
311
|
+
|
|
312
|
+
// ============================================================================
|
|
313
|
+
// Pagination / first-page behavior
|
|
314
|
+
// ============================================================================
|
|
315
|
+
describe("pagination behavior", () => {
|
|
316
|
+
it("parses first page of nodes and does not zero out returned entries", async () => {
|
|
317
|
+
// Simulates response where dynamicFields has one page of nodes (hasNextPage may be true on backend)
|
|
318
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
319
|
+
mockConfigResponse([
|
|
320
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
321
|
+
{ key: { json: "88067" }, value: { json: "100" } },
|
|
322
|
+
]),
|
|
323
|
+
);
|
|
324
|
+
|
|
325
|
+
const result = await getEnergyConfig();
|
|
326
|
+
|
|
327
|
+
expect(result).toEqual({ 77917: 500, 88067: 100 });
|
|
328
|
+
expect(result[77917]).toBe(500);
|
|
329
|
+
expect(result[88067]).toBe(100);
|
|
330
|
+
});
|
|
331
|
+
|
|
332
|
+
it("returns empty object when first object has no nodes (missing path) without throwing", async () => {
|
|
333
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue({
|
|
334
|
+
data: {
|
|
335
|
+
objects: {
|
|
336
|
+
nodes: [
|
|
337
|
+
{
|
|
338
|
+
address: "0xcfg",
|
|
339
|
+
asMoveObject: {
|
|
340
|
+
contents: {
|
|
341
|
+
extract: undefined,
|
|
342
|
+
},
|
|
343
|
+
},
|
|
344
|
+
},
|
|
345
|
+
],
|
|
346
|
+
},
|
|
347
|
+
},
|
|
348
|
+
} as unknown as Awaited<
|
|
349
|
+
ReturnType<typeof getSingletonConfigObjectByType>
|
|
350
|
+
>);
|
|
351
|
+
|
|
352
|
+
const result = await getEnergyConfig();
|
|
353
|
+
|
|
354
|
+
expect(result).toEqual({});
|
|
355
|
+
});
|
|
356
|
+
});
|
|
357
|
+
|
|
358
|
+
// ============================================================================
|
|
359
|
+
// getEnergyUsageForType / getFuelEfficiencyForType
|
|
360
|
+
// ============================================================================
|
|
361
|
+
describe("getEnergyUsageForType / getFuelEfficiencyForType", () => {
|
|
362
|
+
it("getEnergyUsageForType returns 0 for typeId not in config", async () => {
|
|
363
|
+
vi.mocked(getSingletonConfigObjectByType).mockResolvedValue(
|
|
364
|
+
mockConfigResponse([
|
|
365
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
366
|
+
]),
|
|
367
|
+
);
|
|
368
|
+
|
|
369
|
+
const missing = await getEnergyUsageForType(99999);
|
|
370
|
+
|
|
371
|
+
expect(missing).toBe(0);
|
|
372
|
+
});
|
|
373
|
+
|
|
374
|
+
it("getFuelEfficiencyForType returns cached value for typeId in config", async () => {
|
|
375
|
+
vi.mocked(getSingletonConfigObjectByType)
|
|
376
|
+
.mockResolvedValueOnce(
|
|
377
|
+
mockConfigResponse([
|
|
378
|
+
{ key: { json: "77917" }, value: { json: "500" } },
|
|
379
|
+
]),
|
|
380
|
+
)
|
|
381
|
+
.mockResolvedValueOnce({
|
|
382
|
+
data: {
|
|
383
|
+
objects: {
|
|
384
|
+
nodes: [
|
|
385
|
+
{
|
|
386
|
+
address: "0xfuel",
|
|
387
|
+
asMoveObject: {
|
|
388
|
+
contents: {
|
|
389
|
+
extract: {
|
|
390
|
+
extract: {
|
|
391
|
+
asAddress: {
|
|
392
|
+
addressAt: {
|
|
393
|
+
dynamicFields: {
|
|
394
|
+
pageInfo: {
|
|
395
|
+
hasNextPage: false,
|
|
396
|
+
endCursor: null,
|
|
397
|
+
},
|
|
398
|
+
nodes: [
|
|
399
|
+
{
|
|
400
|
+
key: { json: "77917" },
|
|
401
|
+
value: { json: "75" },
|
|
402
|
+
},
|
|
403
|
+
],
|
|
404
|
+
},
|
|
405
|
+
},
|
|
406
|
+
},
|
|
407
|
+
},
|
|
408
|
+
},
|
|
409
|
+
},
|
|
410
|
+
},
|
|
411
|
+
},
|
|
412
|
+
],
|
|
413
|
+
},
|
|
414
|
+
},
|
|
415
|
+
});
|
|
416
|
+
|
|
417
|
+
const energy = await getEnergyUsageForType(77917);
|
|
418
|
+
const fuelEff = await getFuelEfficiencyForType(77917);
|
|
419
|
+
|
|
420
|
+
expect(energy).toBe(500);
|
|
421
|
+
expect(fuelEff).toBe(75);
|
|
422
|
+
});
|
|
423
|
+
});
|
|
424
|
+
});
|
|
@@ -95,9 +95,9 @@ describe("mapping utilities", () => {
|
|
|
95
95
|
).toBe(Assemblies.NetworkNode);
|
|
96
96
|
});
|
|
97
97
|
|
|
98
|
-
it("returns
|
|
98
|
+
it("returns Assembly as default for unknown type", () => {
|
|
99
99
|
expect(getAssemblyType("unknown::type::Unknown")).toBe(
|
|
100
|
-
Assemblies.
|
|
100
|
+
Assemblies.Assembly,
|
|
101
101
|
);
|
|
102
102
|
});
|
|
103
103
|
|
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
const MS_PER_HOUR = 3600000;
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* Result of adjusting raw burn time for efficiency.
|
|
5
|
+
* - burnTimePerUnitMs: efficiency-adjusted milliseconds to burn one unit
|
|
6
|
+
* - unitsPerHour: how many units are burned per hour at this rate
|
|
7
|
+
*/
|
|
8
|
+
export interface AdjustedBurnRate {
|
|
9
|
+
burnTimePerUnitMs: number;
|
|
10
|
+
unitsPerHour: number;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* Takes raw burn time (ms per unit at 100%) and efficiency (0–100 scale),
|
|
15
|
+
* returns efficiency-adjusted burn time per unit and units burned per hour.
|
|
16
|
+
* E.g. raw 3600000 ms, 90% efficient → burn time 3240000 ms, ~1.11 units/hour.
|
|
17
|
+
*
|
|
18
|
+
* @param rawBurnTimeMs - Milliseconds to burn one unit at 100% efficiency. If non-finite or negative, treated as 0 so both returned fields are finite and consistent.
|
|
19
|
+
* @param efficiencyPercent - Efficiency as percentage on a 0–100 scale (e.g. 90 for 90%). Values outside [0, 100] are treated as invalid; when null/undefined, not finite, ≤0, or >100, raw is used as-is for burn time.
|
|
20
|
+
* @returns { burnTimePerUnitMs, unitsPerHour }
|
|
21
|
+
* @category Utilities
|
|
22
|
+
*/
|
|
23
|
+
export function getAdjustedBurnRate(
|
|
24
|
+
rawBurnTimeMs: number,
|
|
25
|
+
efficiencyPercent: number | null | undefined,
|
|
26
|
+
): AdjustedBurnRate {
|
|
27
|
+
const raw =
|
|
28
|
+
Number.isFinite(rawBurnTimeMs) && rawBurnTimeMs >= 0 ? rawBurnTimeMs : 0;
|
|
29
|
+
|
|
30
|
+
const validEfficiency =
|
|
31
|
+
efficiencyPercent != null &&
|
|
32
|
+
Number.isFinite(efficiencyPercent) &&
|
|
33
|
+
efficiencyPercent > 0 &&
|
|
34
|
+
efficiencyPercent <= 100;
|
|
35
|
+
|
|
36
|
+
const burnTimePerUnitMs = validEfficiency
|
|
37
|
+
? raw * (efficiencyPercent / 100)
|
|
38
|
+
: raw;
|
|
39
|
+
|
|
40
|
+
const unitsPerHour =
|
|
41
|
+
burnTimePerUnitMs > 0 && Number.isFinite(burnTimePerUnitMs)
|
|
42
|
+
? MS_PER_HOUR / burnTimePerUnitMs
|
|
43
|
+
: 0;
|
|
44
|
+
|
|
45
|
+
return { burnTimePerUnitMs, unitsPerHour };
|
|
46
|
+
}
|