waterFacilityStatistics.test.mjs 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301
  1. import test from 'node:test'
  2. import assert from 'node:assert/strict'
  3. import fs from 'node:fs'
  4. import {
  5. buildPipeNetworkStatisticsQuery,
  6. FACILITY_SECTION_LABELS,
  7. FACILITY_SECTION_TYPES,
  8. FACILITY_STATUS_TAG_TYPES,
  9. getFacilityStatusOptions,
  10. flattenStatisticMetrics,
  11. formatStatisticRow,
  12. normalizeStatisticKey,
  13. resolveDistributionValueLabel,
  14. resolveFacilitySectionLabel,
  15. resolveStatisticMetricDescription,
  16. resolveStatisticMetricLabel,
  17. resolveFacilityStatusLabel
  18. } from '../src/utils/waterFacilityStatisticsModel.js'
  19. test('statistics query mirrors dashboard and export filters', () => {
  20. assert.deepEqual(
  21. buildPipeNetworkStatisticsQuery({
  22. facilityTypes: ['plant', 'pipe', ''],
  23. status: '1'
  24. }),
  25. { facilityTypes: 'plant,pipe', status: '1' }
  26. )
  27. })
  28. test('statistics status filter returns to the all-state contract', () => {
  29. assert.equal(buildPipeNetworkStatisticsQuery({ facilityTypes: ['pipe'], status: '' }).status, undefined)
  30. assert.equal(buildPipeNetworkStatisticsQuery({ facilityTypes: ['pipe'], status: null }).status, undefined)
  31. })
  32. test('statistics query omits empty filters instead of requesting empty datasets', () => {
  33. assert.deepEqual(buildPipeNetworkStatisticsQuery(), {})
  34. assert.deepEqual(buildPipeNetworkStatisticsQuery({ facilityTypes: ['', ' '], status: '' }), {})
  35. })
  36. test('statistics page prevents empty facility-type requests and export', () => {
  37. const source = fs.readFileSync(
  38. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  39. 'utf8'
  40. )
  41. assert.match(source, /buildPipeNetworkStatisticsQuery/u)
  42. assert.match(source, /:disabled="!selectedTypes\.length"/u)
  43. assert.match(source, /请至少选择一类设施/u)
  44. assert.match(source, /if \(!selectedTypes\.value\.length\)/u)
  45. })
  46. test('statistics status filter follows the facility status contract', () => {
  47. const source = fs.readFileSync(
  48. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  49. 'utf8'
  50. )
  51. assert.match(source, /getFacilityStatusOptions/u)
  52. assert.match(source, /resolveFacilityStatusLabel/u)
  53. assert.match(source, /label="展示全部" value=""/u)
  54. assert.doesNotMatch(source, /@clear="/u)
  55. assert.match(source, /warningCount/u)
  56. assert.match(source, /unknownCount/u)
  57. })
  58. test('statistics table uses Chinese field labels and explicit hints', () => {
  59. const source = fs.readFileSync(
  60. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  61. 'utf8'
  62. )
  63. assert.match(source, /formatStatisticRow/u)
  64. assert.match(source, /el-tooltip content="按设施类型汇总的关键统计指标"/u)
  65. assert.match(source, /el-tooltip content="当前行的统计口径和数据来源"/u)
  66. })
  67. test('statistics status rows use the agreed unknown label', () => {
  68. const source = fs.readFileSync(
  69. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  70. 'utf8'
  71. )
  72. const model = fs.readFileSync('src/utils/waterFacilityStatisticsModel.js', 'utf8')
  73. assert.match(model, /resolveFacilityStatusLabel\(facilityType, item\.name\)/u)
  74. assert.match(source, /category === 'normal' \? '正常' : '未知'/u)
  75. assert.doesNotMatch(source, /未填报/u)
  76. })
  77. test('statistics status filter returns to the unfiltered contract', () => {
  78. assert.deepEqual(
  79. buildPipeNetworkStatisticsQuery({ facilityTypes: ['plant'], status: null }),
  80. { facilityTypes: 'plant' }
  81. )
  82. assert.doesNotMatch(
  83. JSON.stringify(buildPipeNetworkStatisticsQuery({ facilityTypes: ['plant'], status: '' })),
  84. /"status"/u
  85. )
  86. })
  87. test('facility status labels follow the per-facility-type contract', () => {
  88. assert.equal(resolveFacilityStatusLabel('source', 0), '正常')
  89. assert.equal(resolveFacilityStatusLabel('source', 1), '异常')
  90. assert.equal(resolveFacilityStatusLabel('plant', 1), '停产')
  91. assert.equal(resolveFacilityStatusLabel('pumpStation', 1), '故障停运')
  92. assert.equal(resolveFacilityStatusLabel('pumpStation', 2), '检修中')
  93. assert.equal(resolveFacilityStatusLabel('pipe', 1), '维护中')
  94. assert.equal(resolveFacilityStatusLabel('pipe', 2), '异常')
  95. assert.equal(resolveFacilityStatusLabel('user', 1), '欠费')
  96. assert.equal(resolveFacilityStatusLabel('user', 2), '停用')
  97. assert.equal(resolveFacilityStatusLabel('source', null), '未知')
  98. })
  99. test('dashboard sections map to the shared facility status contract', () => {
  100. assert.deepEqual(FACILITY_SECTION_TYPES, {
  101. pipeNetwork: 'pipe',
  102. waterPlant: 'plant',
  103. waterSource: 'source',
  104. waterUser: 'user',
  105. pumpStation: 'pumpStation'
  106. })
  107. assert.equal(resolveFacilityStatusLabel(FACILITY_SECTION_TYPES.waterSource, 1), '异常')
  108. assert.equal(resolveFacilityStatusLabel(FACILITY_SECTION_TYPES.waterPlant, 1), '停产')
  109. assert.equal(resolveFacilityStatusLabel(FACILITY_SECTION_TYPES.pipeNetwork, 1), '维护中')
  110. assert.equal(resolveFacilityStatusLabel(FACILITY_SECTION_TYPES.pipeNetwork, 2), '异常')
  111. assert.equal(resolveFacilityStatusLabel(FACILITY_SECTION_TYPES.waterUser, 1), '欠费')
  112. assert.deepEqual(FACILITY_SECTION_LABELS, {
  113. pipeNetwork: '管网',
  114. waterPlant: '水厂',
  115. waterSource: '水源地',
  116. waterUser: '用水户',
  117. pumpStation: '泵站'
  118. })
  119. })
  120. test('facility status colors follow the per-facility-type contract', () => {
  121. assert.deepEqual(FACILITY_STATUS_TAG_TYPES.source, { 0: 'success', 1: 'danger' })
  122. assert.deepEqual(FACILITY_STATUS_TAG_TYPES.plant, { 0: 'success', 1: 'danger' })
  123. assert.deepEqual(FACILITY_STATUS_TAG_TYPES.pumpStation, {
  124. 0: 'success',
  125. 1: 'danger',
  126. 2: 'warning'
  127. })
  128. assert.deepEqual(FACILITY_STATUS_TAG_TYPES.pipe, {
  129. 0: 'success',
  130. 1: 'warning',
  131. 2: 'danger'
  132. })
  133. assert.deepEqual(FACILITY_STATUS_TAG_TYPES.user, {
  134. 0: 'success',
  135. 1: 'warning',
  136. 2: 'danger'
  137. })
  138. })
  139. test('status options only expose values valid for selected facility types', () => {
  140. assert.deepEqual(getFacilityStatusOptions(['source']), [
  141. { value: '0', label: '水源地: 正常' },
  142. { value: '1', label: '水源地: 异常' }
  143. ])
  144. assert.deepEqual(getFacilityStatusOptions(['source', 'pumpStation', 'user']), [
  145. { value: '0', label: '正常' },
  146. { value: '1', label: '异常 / 故障停运 / 欠费' },
  147. { value: '2', label: '检修中 / 停用' }
  148. ])
  149. assert.deepEqual(getFacilityStatusOptions([]), [])
  150. })
  151. test('dashboard provides an explicit path back to the unfiltered state', () => {
  152. const source = fs.readFileSync(
  153. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  154. 'utf8'
  155. )
  156. assert.match(source, /@change="handleStatusChange"/u)
  157. assert.match(source, /statusFilter\.value = String\(value \?\? ''\)/u)
  158. })
  159. test('changing facility types normalizes a stale status filter before loading data', () => {
  160. const source = fs.readFileSync(
  161. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  162. 'utf8'
  163. )
  164. assert.match(source, /@change="handleTypeChange"/u)
  165. assert.match(
  166. source,
  167. /const handleTypeChange = \(\) => \{\s*const options = getFacilityStatusOptions\(selectedTypes\.value\)\s*if \(!options\.some\(option => option\.value === statusFilter\.value\)\) statusFilter\.value = ''\s*loadData\(\)\s*\}/u
  168. )
  169. })
  170. test('statistics cards and charts use a non-overlapping technical layout', () => {
  171. const source = fs.readFileSync(
  172. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  173. 'utf8'
  174. )
  175. assert.match(source, /\.metric-icon \{\s*[^}]*position: static/u)
  176. assert.match(source, /grid-template-columns: repeat\(5, minmax\(160px, 1fr\)\)/u)
  177. assert.match(source, /radius: \['52%', '72%'\]/u)
  178. assert.doesNotMatch(source, /roseType:/u)
  179. })
  180. test('statistics charts hide status series without data', () => {
  181. const source = fs.readFileSync(
  182. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  183. 'utf8'
  184. )
  185. const model = fs.readFileSync('src/utils/waterFacilityStatisticsModel.js', 'utf8')
  186. assert.match(source, /\.filter\(series => facilityOverview\.some\(item => Number\(item\[series\.key\] \|\| 0\) > 0\)\)/u)
  187. assert.match(model, /resolveFacilityStatusLabel\(facilityType, item\.name\)/u)
  188. assert.doesNotMatch(source, /未填报/u)
  189. })
  190. test('statistics metric keys accept both snake_case and camelCase', () => {
  191. assert.equal(normalizeStatisticKey('total_count'), 'totalCount')
  192. assert.equal(normalizeStatisticKey('totalCount'), 'totalCount')
  193. assert.equal(normalizeStatisticKey('annual_water_consumption'), 'annualWaterConsumption')
  194. assert.equal(resolveStatisticMetricLabel('total_count'), '总数')
  195. assert.equal(resolveStatisticMetricLabel('total_length'), '总长度')
  196. assert.equal(resolveStatisticMetricLabel('average_diameter'), '平均管径')
  197. assert.equal(resolveStatisticMetricLabel('design_capacity'), '设计规模')
  198. assert.equal(resolveStatisticMetricLabel('current_capacity'), '实际规模')
  199. assert.equal(resolveStatisticMetricLabel('pump_count'), '水泵数量')
  200. assert.equal(resolveStatisticMetricLabel('design_flow'), '设计流量')
  201. assert.equal(resolveStatisticMetricLabel('design_head'), '设计扬程')
  202. assert.equal(resolveStatisticMetricLabel('power_capacity'), '装机容量')
  203. assert.equal(resolveStatisticMetricLabel('annual_water_consumption'), '年用水量')
  204. assert.equal(resolveStatisticMetricLabel('capacity_utilization'), '产能利用率')
  205. assert.equal(resolveStatisticMetricLabel('totalCount'), '总数')
  206. })
  207. test('statistics detail table never falls back to english keys or codes', () => {
  208. assert.equal(resolveStatisticMetricLabel('unknown_metric_xyz'), '其他指标')
  209. assert.equal(resolveStatisticMetricDescription('unknown_metric_xyz'), '按当前筛选条件统计')
  210. assert.equal(resolveStatisticMetricDescription('total_length'), '当前筛选条件下的管网总长度')
  211. assert.equal(resolveFacilitySectionLabel('unknownSection'), '总览')
  212. assert.equal(resolveFacilitySectionLabel('pipeNetwork'), '管网')
  213. })
  214. test('statistics distribution groups translate coded values', () => {
  215. assert.equal(resolveDistributionValueLabel('materialDistribution', 'PE'), '聚乙烯')
  216. assert.equal(resolveDistributionValueLabel('materialDistribution', 'PVC-U'), '硬聚氯乙烯')
  217. assert.equal(resolveDistributionValueLabel('materialDistribution', '铸铁'), '铸铁')
  218. assert.equal(resolveDistributionValueLabel('materialDistribution', null), '未填报')
  219. assert.equal(resolveDistributionValueLabel('waterSourceTypeDistribution', '地下水'), '地下水')
  220. })
  221. test('statistics detail rows render backend payload without english text', () => {
  222. const payload = {
  223. pipeNetwork: {
  224. total_count: 996,
  225. total_length: 18819.43,
  226. average_diameter: 334.53,
  227. materialDistribution: [
  228. { name: 'PE', value: 277, unit: '条' },
  229. { name: '铸铁', value: 183, unit: '条' }
  230. ],
  231. statusDistribution: [{ name: '2', value: 4, unit: '条' }]
  232. },
  233. waterPlant: {
  234. design_capacity: 5,
  235. current_capacity: 4,
  236. capacity_utilization: 80,
  237. capacityUtilization: { value: 80, unit: '%' }
  238. },
  239. waterSource: { qualityDistribution: [{ name: 'Ⅳ类', value: 1, unit: '处' }] }
  240. }
  241. const rows = Object.entries(payload)
  242. .flatMap(([section, value]) => flattenStatisticMetrics(section, value))
  243. .map(item => formatStatisticRow(item.section, item, { levelLabel: name => `第${name}级` }))
  244. const cells = rows.map(row => `${row.section}|${row.name}|${row.description}`).join('\n')
  245. assert.doesNotMatch(cells, /[A-Za-z]{2,}/u)
  246. assert.doesNotMatch(cells, /\bvalue\b|\bunit\b/u)
  247. assert.equal(rows.find(row => row.section === '管网 - 总数').name, '总数')
  248. assert.equal(rows.find(row => row.section === '管网 - 总长度').value, 18819.43)
  249. assert.equal(rows.find(row => row.section === '管网 - 管材分布').name, '聚乙烯')
  250. assert.equal(rows.find(row => row.section === '管网 - 管材分布').value, 277)
  251. assert.equal(rows.find(row => row.section === '管网 - 状态分布').name, '异常')
  252. assert.equal(rows.find(row => row.section === '水厂 - 产能利用率').name, '产能利用率')
  253. assert.equal(rows.find(row => row.section === '水源地 - 水质等级分布').name, 'Ⅳ类')
  254. })
  255. test('statistics detail table uses the shared chinese label helpers', () => {
  256. const source = fs.readFileSync(
  257. 'src/views/subSystem/waterSupply/components/WaterFacilityDashboard.vue',
  258. 'utf8'
  259. )
  260. assert.match(source, /flattenStatisticMetrics/u)
  261. assert.match(source, /formatStatisticRow\(item\.section, item, \{ levelLabel: resolveLevelLabel \}\)/u)
  262. assert.match(source, /resolveLevelLabel/u)
  263. assert.doesNotMatch(source, /STATISTIC_LABELS/u)
  264. assert.doesNotMatch(source, /\|\| rawMetricKey/u)
  265. })