@@ -162,7 +162,7 @@ describe("strappingTable", () => {
162162 } ) ;
163163
164164 it ( "should throw for steps < 1" , ( ) => {
165- expect ( ( ) => strappingTable ( cylinder , { steps : 0 } ) ) . toThrow ( "Steps must be at least 1 " ) ;
165+ expect ( ( ) => strappingTable ( cylinder , { steps : 0 } ) ) . toThrow ( "Steps must be between 1 and 10000 " ) ;
166166 } ) ;
167167
168168 it ( "should work for spherical vessels" , ( ) => {
@@ -175,6 +175,15 @@ describe("strappingTable", () => {
175175 expect ( table [ 0 ] . percentage ) . toBe ( 0 ) ;
176176 expect ( table [ 10 ] . percentage ) . toBeCloseTo ( 100 , 1 ) ;
177177 } ) ;
178+
179+ it ( "should handle max steps" , ( ) => {
180+ const table = strappingTable ( cylinder , { steps : 10000 } ) ;
181+ expect ( table . length ) . toBe ( 10001 ) ;
182+ } ) ;
183+
184+ it ( "should throw for steps > 10000" , ( ) => {
185+ expect ( ( ) => strappingTable ( cylinder , { steps : 10001 } ) ) . toThrow ( "Steps must be between 1 and 10000" ) ;
186+ } ) ;
178187} ) ;
179188
180189describe ( "fillRate" , ( ) => {
@@ -194,110 +203,187 @@ describe("fillRate", () => {
194203 expect ( result . volumeChange ) . toBeGreaterThan ( 0 ) ;
195204 expect ( result . ratePerMinute ) . toBeGreaterThan ( 0 ) ;
196205 expect ( result . ratePerHour ) . toBeCloseTo ( result . ratePerMinute * 60 , 2 ) ;
206+ expect ( result . percentBefore ) . toBe ( 20 ) ;
207+ expect ( result . percentAfter ) . toBe ( 40 ) ;
197208 } ) ;
198209
199210 it ( "should detect draining" , ( ) => {
200211 const result = fillRate ( {
201212 vessel : tank ,
202213 heightBefore : 8 ,
203- heightAfter : 5 ,
214+ heightAfter : 3 ,
204215 timeMinutes : 30 ,
205216 } ) ;
206217 expect ( result . direction ) . toBe ( "draining" ) ;
207218 expect ( result . volumeChange ) . toBeLessThan ( 0 ) ;
208- expect ( result . minutesToEmpty ) . not . toBeNull ( ) ;
219+ expect ( result . ratePerMinute ) . toBeLessThan ( 0 ) ;
220+ expect ( result . ratePerHour ) . toBeCloseTo ( result . ratePerMinute * 60 , 2 ) ;
221+ expect ( result . percentBefore ) . toBe ( 80 ) ;
222+ expect ( result . percentAfter ) . toBe ( 30 ) ;
209223 } ) ;
210224
211- it ( "should detect stable level " , ( ) => {
225+ it ( "should detect stable" , ( ) => {
212226 const result = fillRate ( {
213227 vessel : tank ,
214228 heightBefore : 5 ,
215229 heightAfter : 5 ,
216- timeMinutes : 60 ,
230+ timeMinutes : 10 ,
217231 } ) ;
218232 expect ( result . direction ) . toBe ( "stable" ) ;
219233 expect ( result . volumeChange ) . toBe ( 0 ) ;
234+ expect ( result . ratePerMinute ) . toBe ( 0 ) ;
235+ expect ( result . ratePerHour ) . toBe ( 0 ) ;
236+ expect ( result . minutesToFull ) . toBeNull ( ) ;
237+ expect ( result . minutesToEmpty ) . toBeNull ( ) ;
220238 } ) ;
221239
222- it ( "should calculate ETA to full" , ( ) => {
240+ it ( "should calculate minutes to full for filling tank " , ( ) => {
223241 const result = fillRate ( {
224242 vessel : tank ,
225- heightBefore : 0 ,
243+ heightBefore : 4 ,
226244 heightAfter : 5 ,
227- timeMinutes : 60 ,
245+ timeMinutes : 10 ,
228246 } ) ;
229- expect ( result . direction ) . toBe ( "filling" ) ;
230- expect ( result . minutesToFull ) . not . toBeNull ( ) ;
231- // At this rate (5m in 60min), 5 more meters needed => 60 more min
232- expect ( result . minutesToFull ) . toBeCloseTo ( 60 , 0 ) ;
247+ // 1m height change in 10 min. Total height 10m. Remaining 5m. So 50 min.
248+ expect ( result . minutesToFull ) . toBeCloseTo ( 50 , 2 ) ;
249+ expect ( result . minutesToEmpty ) . toBeCloseTo ( 50 , 2 ) ; // 5m to drain at 1m/10min
233250 } ) ;
234251
235- it ( "should throw for zero time" , ( ) => {
252+ it ( "should calculate minutes to empty for draining tank" , ( ) => {
253+ const result = fillRate ( {
254+ vessel : tank ,
255+ heightBefore : 7 ,
256+ heightAfter : 6 ,
257+ timeMinutes : 5 ,
258+ } ) ;
259+ // 1m height change in 5 min. Remaining 6m. So 30 min.
260+ expect ( result . minutesToEmpty ) . toBeCloseTo ( 30 , 2 ) ;
261+ expect ( result . minutesToFull ) . toBeCloseTo ( 20 , 2 ) ; // 4m to fill at 1m/5min
262+ } ) ;
263+
264+ it ( "should handle zero timeMinutes" , ( ) => {
236265 expect ( ( ) => fillRate ( {
237266 vessel : tank ,
238267 heightBefore : 2 ,
239268 heightAfter : 4 ,
240269 timeMinutes : 0 ,
241- } ) ) . toThrow ( "Time must be positive" ) ;
270+ } ) ) . toThrow ( "timeMinutes must be a positive number " ) ;
242271 } ) ;
243272
244- it ( "should report before/after percentages " , ( ) => {
245- const result = fillRate ( {
273+ it ( "should handle negative timeMinutes " , ( ) => {
274+ expect ( ( ) => fillRate ( {
246275 vessel : tank ,
247276 heightBefore : 2 ,
248- heightAfter : 8 ,
249- timeMinutes : 60 ,
277+ heightAfter : 4 ,
278+ timeMinutes : - 10 ,
279+ } ) ) . toThrow ( "timeMinutes must be a positive number" ) ;
280+ } ) ;
281+
282+ it ( "should handle invalid heightBefore/After" , ( ) => {
283+ expect ( ( ) => fillRate ( {
284+ vessel : tank ,
285+ heightBefore : - 1 ,
286+ heightAfter : 2 ,
287+ timeMinutes : 10 ,
288+ } ) ) . toThrow ( "Liquid height cannot be negative" ) ;
289+
290+ expect ( ( ) => fillRate ( {
291+ vessel : tank ,
292+ heightBefore : 2 ,
293+ heightAfter : 11 ,
294+ timeMinutes : 10 ,
295+ } ) ) . toThrow ( "Liquid height cannot exceed vessel height" ) ;
296+ } ) ;
297+
298+ it ( "should work with different vessel types (e.g., cylindrical)" , ( ) => {
299+ const cylinder : VesselConfig = {
300+ type : "cylindrical" ,
301+ dimensions : { diameter : 2 , height : 10 } ,
302+ orientation : "vertical" ,
303+ } ;
304+ const result = fillRate ( {
305+ vessel : cylinder ,
306+ heightBefore : 4 ,
307+ heightAfter : 6 ,
308+ timeMinutes : 20 ,
250309 } ) ;
251- expect ( result . percentBefore ) . toBeCloseTo ( 20 , 1 ) ;
252- expect ( result . percentAfter ) . toBeCloseTo ( 80 , 1 ) ;
310+ expect ( result . direction ) . toBe ( "filling" ) ;
311+ expect ( result . volumeChange ) . toBeCloseTo ( 6.283 , 2 ) ; // pi * 1^2 * (6-4)
312+ expect ( result . ratePerMinute ) . toBeCloseTo ( 0.314 , 2 ) ;
313+ expect ( result . minutesToFull ) . toBeCloseTo ( 40 , 2 ) ; // 4m remaining, 0.1m/min rate
253314 } ) ;
254315} ) ;
255316
256317describe ( "tankAlarms" , ( ) => {
257- const tank : VesselConfig = {
258- type : "rectangular" ,
259- dimensions : { length : 10 , width : 5 , height : 10 } ,
318+ const config = {
319+ highHigh : 95 ,
320+ high : 90 ,
321+ low : 10 ,
322+ lowLow : 5 ,
260323 } ;
261- const alarms = { highHigh : 95 , high : 80 , low : 20 , lowLow : 5 } ;
262324
263- it ( "should return normal for mid-range level " , ( ) => {
264- const result = tankAlarms ( tank , 5 , alarms ) ;
325+ it ( "should be normal when within bounds " , ( ) => {
326+ const result = tankAlarms ( config , 50 ) ;
265327 expect ( result . status ) . toBe ( "normal" ) ;
266328 expect ( result . isAlarmed ) . toBe ( false ) ;
267- expect ( result . activeAlarms . length ) . toBe ( 0 ) ;
329+ expect ( result . activeAlarms ) . toEqual ( [ ] ) ;
268330 } ) ;
269331
270332 it ( "should detect high alarm" , ( ) => {
271- const result = tankAlarms ( tank , 8.5 , alarms ) ;
333+ const result = tankAlarms ( config , 92 ) ;
272334 expect ( result . status ) . toBe ( "high" ) ;
273335 expect ( result . isAlarmed ) . toBe ( true ) ;
274- expect ( result . activeAlarms ) . toContain ( "high" ) ;
336+ expect ( result . activeAlarms ) . toEqual ( [ "high" ] ) ;
275337 } ) ;
276338
277339 it ( "should detect high-high alarm" , ( ) => {
278- const result = tankAlarms ( tank , 9.8 , alarms ) ;
340+ const result = tankAlarms ( config , 98 ) ;
279341 expect ( result . status ) . toBe ( "high-high" ) ;
280342 expect ( result . isAlarmed ) . toBe ( true ) ;
281- expect ( result . activeAlarms ) . toContain ( "high-high" ) ;
282- expect ( result . activeAlarms ) . toContain ( "high" ) ;
343+ expect ( result . activeAlarms ) . toEqual ( [ "high" , "high-high" ] ) ;
283344 } ) ;
284345
285346 it ( "should detect low alarm" , ( ) => {
286- const result = tankAlarms ( tank , 1.5 , alarms ) ;
347+ const result = tankAlarms ( config , 8 ) ;
287348 expect ( result . status ) . toBe ( "low" ) ;
288349 expect ( result . isAlarmed ) . toBe ( true ) ;
350+ expect ( result . activeAlarms ) . toEqual ( [ "low" ] ) ;
289351 } ) ;
290352
291353 it ( "should detect low-low alarm" , ( ) => {
292- const result = tankAlarms ( tank , 0.3 , alarms ) ;
354+ const result = tankAlarms ( config , 3 ) ;
293355 expect ( result . status ) . toBe ( "low-low" ) ;
294- expect ( result . activeAlarms ) . toContain ( "low-low" ) ;
295- expect ( result . activeAlarms ) . toContain ( "low" ) ;
356+ expect ( result . isAlarmed ) . toBe ( true ) ;
357+ expect ( result . activeAlarms ) . toEqual ( [ "low" , "low-low" ] ) ;
296358 } ) ;
297359
298- it ( "should report percentage" , ( ) => {
299- const result = tankAlarms ( tank , 5 , alarms ) ;
300- expect ( result . percentage ) . toBeCloseTo ( 50 , 1 ) ;
360+ it ( "should handle exact boundary values" , ( ) => {
361+ expect ( tankAlarms ( config , 90 ) . status ) . toBe ( "high" ) ;
362+ expect ( tankAlarms ( config , 95 ) . status ) . toBe ( "high-high" ) ;
363+ expect ( tankAlarms ( config , 10 ) . status ) . toBe ( "low" ) ;
364+ expect ( tankAlarms ( config , 5 ) . status ) . toBe ( "low-low" ) ;
365+ } ) ;
366+
367+ it ( "should handle partial alarm configurations" , ( ) => {
368+ const partialConfig = { high : 80 } ;
369+ expect ( tankAlarms ( partialConfig , 85 ) . status ) . toBe ( "high" ) ;
370+ expect ( tankAlarms ( partialConfig , 70 ) . status ) . toBe ( "normal" ) ;
371+ } ) ;
372+
373+ it ( "should prioritize higher severity alarms" , ( ) => {
374+ const result = tankAlarms ( config , 96 ) ;
375+ expect ( result . status ) . toBe ( "high-high" ) ;
376+ } ) ;
377+
378+ it ( "should handle empty config" , ( ) => {
379+ const result = tankAlarms ( { } , 50 ) ;
380+ expect ( result . status ) . toBe ( "normal" ) ;
381+ expect ( result . isAlarmed ) . toBe ( false ) ;
382+ } ) ;
383+
384+ it ( "should throw for invalid percentage" , ( ) => {
385+ expect ( ( ) => tankAlarms ( config , - 1 ) ) . toThrow ( "Percentage must be between 0 and 100" ) ;
386+ expect ( ( ) => tankAlarms ( config , 101 ) ) . toThrow ( "Percentage must be between 0 and 100" ) ;
301387 } ) ;
302388} ) ;
303389
@@ -312,46 +398,74 @@ describe("tankInventory", () => {
312398 orientation : "vertical" ,
313399 } ;
314400
315- it ( "should calculate inventory across multiple tanks" , ( ) => {
316- const result = tankInventory ( [
317- { name : "Tank A" , vessel : tank1 , liquidHeight : 5 } ,
318- { name : "Tank B" , vessel : tank2 , liquidHeight : 5 } ,
319- ] ) ;
320- expect ( result . count ) . toBe ( 2 ) ;
321- expect ( result . tanks . length ) . toBe ( 2 ) ;
401+ const readings = [
402+ { name : "Tank A" , vessel : tank1 , liquidHeight : 5 , unit : "liters" } ,
403+ { name : "Tank B" , vessel : tank2 , liquidHeight : 5 , unit : "liters" } ,
404+ { name : "Tank C" , vessel : tank1 , liquidHeight : 2 , unit : "cubicMeters" } ,
405+ ] ;
406+
407+ it ( "should calculate total inventory correctly" , ( ) => {
408+ const result = tankInventory ( readings ) ;
409+ expect ( result . count ) . toBe ( 3 ) ;
410+ expect ( result . unit ) . toBe ( "cubicMeters" ) ; // Default unit is cubicMeters
411+
412+ // Tank A: 10*5*5 = 250 m3
413+ // Tank B: pi * 1^2 * 5 = 15.708 m3
414+ // Tank C: 10*5*2 = 100 m3
415+ expect ( result . totalVolume ) . toBeCloseTo ( 250 + 15.708 + 100 , 2 ) ;
416+
417+ // Tank A: 50%
418+ // Tank B: 50%
419+ // Tank C: 20%
420+ expect ( result . averagePercentage ) . toBeCloseTo ( ( 50 + 50 + 20 ) / 3 , 2 ) ;
421+
322422 expect ( result . tanks [ 0 ] . name ) . toBe ( "Tank A" ) ;
423+ expect ( result . tanks [ 0 ] . volume ) . toBeCloseTo ( 250 , 2 ) ;
424+ expect ( result . tanks [ 0 ] . percentage ) . toBe ( 50 ) ;
425+ expect ( result . tanks [ 0 ] . unit ) . toBe ( "cubicMeters" ) ;
426+
323427 expect ( result . tanks [ 1 ] . name ) . toBe ( "Tank B" ) ;
324- expect ( result . totalVolume ) . toBeGreaterThan ( 0 ) ;
325- } ) ;
428+ expect ( result . tanks [ 1 ] . volume ) . toBeCloseTo ( 15.708 , 2 ) ;
429+ expect ( result . tanks [ 1 ] . percentage ) . toBe ( 50 ) ;
430+ expect ( result . tanks [ 1 ] . unit ) . toBe ( "cubicMeters" ) ;
326431
327- it ( "should calculate average percentage" , ( ) => {
328- const result = tankInventory ( [
329- { name : "Tank A" , vessel : tank1 , liquidHeight : 5 } , // 50%
330- { name : "Tank B" , vessel : tank2 , liquidHeight : 5 } , // 50%
331- ] ) ;
332- expect ( result . averagePercentage ) . toBeCloseTo ( 50 , 1 ) ;
432+ expect ( result . tanks [ 2 ] . name ) . toBe ( "Tank C" ) ;
433+ expect ( result . tanks [ 2 ] . volume ) . toBeCloseTo ( 100 , 2 ) ;
434+ expect ( result . tanks [ 2 ] . percentage ) . toBe ( 20 ) ;
435+ expect ( result . tanks [ 2 ] . unit ) . toBe ( "cubicMeters" ) ;
333436 } ) ;
334437
335- it ( "should convert to specified unit" , ( ) => {
336- const result = tankInventory ( [
337- { name : "Tank A" , vessel : tank1 , liquidHeight : 10 } ,
338- ] , "liters" ) ;
339- expect ( result . unit ) . toBe ( "liters" ) ;
340- // 10 * 5 * 10 = 500 m3 = 500000 liters
341- expect ( result . totalVolume ) . toBeGreaterThan ( 100 ) ;
438+ it ( "should allow specifying a target unit" , ( ) => {
439+ const result = tankInventory ( readings , { targetUnit : "gallons" } ) ;
440+ expect ( result . unit ) . toBe ( "gallons" ) ;
441+
442+ // 250 m3 = 66043 gallons
443+ // 15.708 m3 = 4150 gallons
444+ // 100 m3 = 26417 gallons
445+ const expectedTotalGallons = convertVolume ( 250 + 15.708 + 100 , "cubicMeters" , "gallons" ) ;
446+ expect ( result . totalVolume ) . toBeCloseTo ( expectedTotalGallons , 0 ) ;
447+
448+ expect ( result . tanks [ 0 ] . unit ) . toBe ( "gallons" ) ;
449+ expect ( result . tanks [ 0 ] . volume ) . toBeCloseTo ( convertVolume ( 250 , "cubicMeters" , "gallons" ) , 0 ) ;
342450 } ) ;
343451
344- it ( "should handle empty array" , ( ) => {
452+ it ( "should handle empty readings array" , ( ) => {
345453 const result = tankInventory ( [ ] ) ;
346454 expect ( result . count ) . toBe ( 0 ) ;
347455 expect ( result . totalVolume ) . toBe ( 0 ) ;
348456 expect ( result . averagePercentage ) . toBe ( 0 ) ;
349- } ) ;
350-
351- it ( "should include per-tank percentage" , ( ) => {
352- const result = tankInventory ( [
353- { name : "Tank A" , vessel : tank1 , liquidHeight : 2 } ,
354- ] ) ;
355- expect ( result . tanks [ 0 ] . percentage ) . toBeCloseTo ( 20 , 1 ) ;
457+ expect ( result . tanks ) . toEqual ( [ ] ) ;
458+ } ) ;
459+
460+ it ( "should handle readings with invalid liquid heights" , ( ) => {
461+ const invalidReadings = [
462+ { name : "Tank A" , vessel : tank1 , liquidHeight : - 1 , unit : "liters" } ,
463+ { name : "Tank B" , vessel : tank2 , liquidHeight : 5 , unit : "liters" } ,
464+ ] ;
465+ // Expect the invalid tank to be skipped or result in 0 volume/percentage
466+ const result = tankInventory ( invalidReadings ) ;
467+ expect ( result . count ) . toBe ( 1 ) ; // Only Tank B is valid
468+ expect ( result . tanks [ 0 ] . name ) . toBe ( "Tank B" ) ;
469+ expect ( result . tanks [ 0 ] . volume ) . toBeCloseTo ( 15.708 , 2 ) ;
356470 } ) ;
357471} ) ;
0 commit comments