[Perl] DAQ system for the FAPG.
1
const=require"node:assert/strict";
2
const{}=require"node:test";
3
const=require"node:vm";
4
const=require"node:fs/promises";
5
const=require"node:path";
6
const{,}=require"node:url";
7
8
const=new Map;
9
10
asyncfunction(file){
11
const=path.resolvefile;
12
ifmoduleCache.hasabsolutereturnmoduleCache.getabsolute;
13
14
const=awaitfs.readFileabsolute,"utf8";
15
const=new vm.SourceTextModulesource, {
16
pathToFileURLabsolute.href
17
};
18
moduleCache.setabsolute,module;
19
20
awaitmodule.link(specifier,referencingModule)=>{
21
const=fileURLToPathnew URLspecifier, referencingModule.identifier;
22
returnloadModuledependency;
23
};
24
awaitmodule.evaluate;
25
returnmodule;
26
}
27
28
function(name){
29
returnloadModulepath.join__dirname,"../../public/js/dashboard",name;
30
}
31
32
test"status state trusts API unreachable status", async()=>{
33
const=awaitdashboardModule"status-state.js";
34
const{}=module.namespace;
35
const=new Date.toISOString;
36
37
assert.equalstateForNodeStatusnull,"unreachable";
38
assert.equal
39
stateForNodeStatus{"unreachable",current},
40
"unreachable"
41
;
42
assert.equalstateForNodeStatus{"ok",current},"healthy";
43
assert.equal
44
stateForNodeStatus{"probe_error",current},
45
"probe-error"
46
;
47
assert.equalstateForNodeStatus{"ok","invalid"},"unreachable";
48
};
49
50
test"timeframeToHours includes the selected range", async()=>{
51
const=awaitdashboardModule"timeframe.js";
52
const{}=module.namespace;
53
54
assert.equaltimeframeToHours{()=>"hour",()=>1},1;
55
assert.equaltimeframeToHours{()=>"day",()=>2},48;
56
assert.equaltimeframeToHours{()=>"week",()=>3},504;
57
assert.equaltimeframeToHours{()=>"unknown",()=>1},24;
58
};
59
60
test"adjust steps to next/previous timeframe at range bounds", async()=>{
61
// We need a DOM element for createTimeframeState
62
const{}=awaitimport"jsdom".catch()=>{null};
63
64
// Test the logic directly using a minimal mock
65
const=awaitdashboardModule"timeframe.js";
66
const{}=module.namespace;
67
68
// createTimeframeState needs document.querySelector — skip if unavailable
69
iftypeofglobalThis.document==="undefined"{
70
// Patch minimal document
71
globalThis.document={()=>{{"day"}}};
72
}
73
74
const=createTimeframeState;
75
assert.equalstate.selected,"day";
76
assert.equalstate.range,1;
77
78
// Decrease below range 1 should step down to hour at threshold-1 (23)
79
state.adjust"day",-1;
80
assert.equalstate.selected,"hour";
81
assert.equalstate.range,23;
82
83
// Increase from 23 hours to 24 should promote to day
84
state.adjust"hour",1;
85
assert.equalstate.selected,"day";
86
assert.equalstate.range,1;
87
88
// Decrease at hour/1 should stay at hour (lowest)
89
state.select"hour";
90
state.ranges.hour=1;
91
state.adjust"hour",-1;
92
assert.equalstate.selected,"hour";
93
assert.equalstate.range,1;
94
95
// 6 days + 1 = 7 days → promotes to week
96
state.select"day";
97
state.ranges.day=6;
98
state.adjust"day",1;
99
assert.equalstate.selected,"week";
100
assert.equalstate.range,1;
101
102
// 3 weeks + 1 = 4 weeks → promotes to month
103
state.select"week";
104
state.ranges.week=3;
105
state.adjust"week",1;
106
assert.equalstate.selected,"month";
107
assert.equalstate.range,1;
108
109
// 11 months + 1 = 12 → promotes to year
110
state.select"month";
111
state.ranges.month=11;
112
state.adjust"month",1;
113
assert.equalstate.selected,"year";
114
assert.equalstate.range,1;
115
116
// Demoting from week should land at day/6
117
state.select"week";
118
state.ranges.week=1;
119
state.adjust"week",-1;
120
assert.equalstate.selected,"day";
121
assert.equalstate.range,6;
122
123
deleteglobalThis.document;
124
};
125
126
test"tooltipOptions returns independent callback configuration", async()=>{
127
const=awaitdashboardModule"chart-options.js";
128
const{,}=module.namespace;
129
const=()=>"";
130
const=tooltipOptions{title};
131
132
assert.equaloptions.mode,"index";
133
assert.equaloptions.callbacks.title,title;
134
assert.equalTOOLTIP_DEFAULTS.callbacks,undefined;
135
};
136
137
test"compact weather scales reclaim secondary-axis width", async()=>{
138
const=awaitdashboardModule"weather-page.js";
139
const{}=module.namespace;
140
const=weatherScaleOptionstrue;
141
const=weatherScaleOptionsfalse;
142
143
assert.equalcompact.yHumid.display,false;
144
assert.equalcompact.yRain.display,false;
145
assert.equalcompact.yTemp.title.display,false;
146
assert.equalcompact.x.ticks.maxRotation,0;
147
assert.equaldesktop.yHumid.display,true;
148
assert.equaldesktop.yRain.display,true;
149
};
150
151
test"compact single-axis insights scales drop the axis title", async()=>{
152
const=awaitdashboardModule"insights.js";
153
const{}=module.namespace;
154
155
const=insightsSingleAxisScalestrue,{
156
"Volatility (σ)",
157
{true}
158
};
159
const=insightsSingleAxisScalesfalse,{"Volatility (σ)"};
160
161
assert.equalcompact.y.title.display,false;
162
assert.equalcompact.x.ticks.maxRotation,0;
163
assert.equalcompact.x.ticks.maxTicksLimit,6;
164
// Extra y options are preserved through the merge.
165
assert.equalcompact.y.beginAtZero,true;
166
167
assert.equaldesktop.y.title.display,true;
168
assert.equaldesktop.y.title.text,"Volatility (σ)";
169
assert.equaldesktop.x.ticks.maxRotation,45;
170
};
171
172
test"compact overlay insights scales collapse secondary axes", async()=>{
173
const=awaitdashboardModule"insights.js";
174
const{}=module.namespace;
175
176
const={"yDO","DO (mg/L)","#0ea5e9"};
177
const=
178
{"yORP","ORP (mV)","#f59e0b"},
179
{"yTemp","°C","#64748b"}
180
;
181
182
const=insightsOverlayScalestrue,primary,secondaries;
183
const=insightsOverlayScalesfalse,primary,secondaries;
184
185
// Primary axis stays visible but loses its title on phones.
186
assert.equalcompact.yDO.title.display,false;
187
assert.equalcompact.yDO.position,"left";
188
assert.equalcompact.yDO.ticks.color,"#0ea5e9";
189
190
// Secondary right-hand axes are hidden entirely to reclaim width.
191
assert.equalcompact.yORP.display,false;
192
assert.equalcompact.yTemp.display,false;
193
194
// Desktop keeps everything.
195
assert.equaldesktop.yDO.title.display,true;
196
assert.equaldesktop.yORP.display,true;
197
assert.equaldesktop.yTemp.display,true;
198
assert.equaldesktop.yTemp.title.text,"°C";
199
};
200
201
test"compact viewport helper degrades gracefully without matchMedia", async()=>{
202
const=awaitdashboardModule"viewport.js";
203
const{,}=module.namespace;
204
205
assert.equalCOMPACT_MEDIA_QUERY,"(max-width: 575.98px)";
206
// No window in the test VM → must not throw, returns false.
207
assert.equalisCompactViewport,false;
208
};
209