fix(mocks): keep a leading blank, and round a date the way the text branch does
Three findings from review. - viewdata trimmed both ends of a character cell. A leading blank is part of a SAS character value and the viewer renders it; only the trailing blanks SAS pads to are noise, so the cell is trimmed at the end and an all-blank cell is still SAS missing. viewdata-cells.test.js fails on the previous version. - viewDate floored a stored day count while the ISO branch rounded, so the same instant could land on different days depending on how the row carried it. Both round now. - the two label members stay different. The finding was that cols[].LABEL uses the data dictionary business label while $viewdata.vars[.].label uses the physical one; the recorded estate response does exactly that - cols DSN is "Dataset Name to be edited", $viewdata.vars.DSN.label is "DSN" - so unifying them would break parity. The comment says so, and a test asserts the split against the recorded answer so the next reader does not tidy it away.
This commit is contained in:
1 parent
40550ca56b
commit
4b71cf5b03
2 files changed
+73
-3
No files matched your search
@@ -51,7 +51,9 @@ function viewDateTime(seconds) {
|
||||
}
|
||||
|
||||
function viewDate(seconds) {
|
||||
const d = new Date(SAS_EPOCH_MS + Math.floor(seconds) * 86400000)
|
||||
// whole days, rounded as the text branch rounds, so a stored number and a
|
||||
// stored ISO string of the same instant land on the same day
|
||||
const d = new Date(SAS_EPOCH_MS + Math.round(seconds) * 86400000)
|
||||
// DATE9. is four digits wide, so a year below 1000 is zero-padded
|
||||
return pad2(d.getUTCDate()) + VIEW_MONTHS[d.getUTCMonth()] + pad4(d.getUTCFullYear())
|
||||
}
|
||||
@@ -282,9 +284,15 @@ if (tableData && tableData.columns) {
|
||||
if (val === undefined || val === null || val === '') {
|
||||
row[col.name] = col.type === 'N' ? '.' : ' '
|
||||
} else {
|
||||
/*
|
||||
Only the trailing blanks are noise - the width SAS pads to.
|
||||
A leading blank is part of the value and the viewer shows it,
|
||||
so the text is trimmed at the end only.
|
||||
*/
|
||||
const text = String(val).replace(/\s+$/, '')
|
||||
row[col.name] = typeof val === 'number'
|
||||
? String(val)
|
||||
: (String(val).trim() === '' ? ' ' : String(val).trim())
|
||||
? String(val)
|
||||
: (text === '' ? ' ' : text)
|
||||
}
|
||||
}
|
||||
return row
|
||||
@@ -310,6 +318,13 @@ if (tableData && tableData.columns) {
|
||||
bytes, DATETIME19.3. sasVarsEntry describes the *edit grid*, where the
|
||||
temporal columns have been converted to character, so it is not used here.
|
||||
*/
|
||||
/*
|
||||
The two label members are deliberately different, and the recorded
|
||||
response proves it: cols[].LABEL for DSN is "Dataset Name to be edited"
|
||||
(the data dictionary's business label, which viewdata.sas augments in as
|
||||
it does for getdata) while $viewdata.vars.DSN.label is "DSN", the physical
|
||||
label. Unifying them would look tidier and would stop matching the estate.
|
||||
*/
|
||||
vars = {}
|
||||
for (const col of physicalColumns) {
|
||||
const isTemporal = ['DATE', 'DATETIME', 'TIME'].indexOf(getDdType(col)) >= 0
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
/**
|
||||
* Character cells and labels in the viewer's payload.
|
||||
*
|
||||
* Two rules the recorded response fixes, both of which look like bugs from the
|
||||
* inside and are parity from the outside.
|
||||
*/
|
||||
const test = require('node:test')
|
||||
const assert = require('node:assert')
|
||||
const fs = require('node:fs')
|
||||
const path = require('node:path')
|
||||
|
||||
const { seededEstate, runService } = require('./harness')
|
||||
|
||||
function viewOf(estate, libds) {
|
||||
const body = JSON.parse(
|
||||
runService(estate, 'public/viewdata', {
|
||||
tableName: 'SASControlTable',
|
||||
rows: [{ LIBDS: libds, FILTER_RK: -1 }]
|
||||
})
|
||||
)
|
||||
assert.equal(body.sasjsAbort, undefined)
|
||||
return body
|
||||
}
|
||||
|
||||
test('a character cell keeps a leading blank and loses only the padding', () => {
|
||||
const estate = seededEstate({ demo: false })
|
||||
const table = {
|
||||
columns: [{ name: 'C', type: 'char', length: 10 }],
|
||||
rows: [{ C: ' ABC' }, { C: 'ABC ' }, { C: ' ' }]
|
||||
}
|
||||
fs.writeFileSync(
|
||||
path.join(estate.appDir, 'data', 'DC_JSLIB', 'mpe_x_cells.json'),
|
||||
JSON.stringify(table)
|
||||
)
|
||||
|
||||
const rows = viewOf(estate, 'DC_JSLIB.MPE_X_CELLS').viewdata
|
||||
|
||||
// a leading blank is part of the value, and the viewer shows it
|
||||
assert.equal(rows[0].C, ' ABC')
|
||||
// trailing blanks are the width SAS pads to
|
||||
assert.equal(rows[1].C, 'ABC')
|
||||
// an all-blank cell is SAS missing
|
||||
assert.equal(rows[2].C, ' ')
|
||||
})
|
||||
|
||||
test('cols and $viewdata report the labels the estate reports', () => {
|
||||
// the recorded response has cols[].LABEL for DSN as the data dictionary's
|
||||
// business label and $viewdata.vars.DSN.label as the physical label - two
|
||||
// members, two answers, and the estate is the authority
|
||||
const body = viewOf(seededEstate({ demo: false }), 'DC_JSLIB.MPE_TABLES')
|
||||
|
||||
const dsn = body.cols.find((c) => c.NAME === 'DSN')
|
||||
assert.equal(dsn.LABEL, 'Dataset Name to be edited')
|
||||
assert.equal(body.$viewdata.vars.DSN.label, 'DSN')
|
||||
})
|
||||
Reference in new issue
Block a user