From 2ee127b8c4e9059aa9da47cbae5d7e8eb0d90114 Mon Sep 17 00:00:00 2001 From: Jos de Jong Date: Fri, 26 Jul 2024 21:32:18 +0200 Subject: [PATCH] chore: fix not always remembering the initial `enforceString` state --- .../__snapshots__/JSONEditor.test.ts.snap | 62 +++++++++---------- src/lib/logic/documentState.test.ts | 29 ++++++++- src/lib/logic/documentState.ts | 14 +++-- src/lib/logic/table.ts | 4 +- src/lib/utils/numberUtils.ts | 2 +- src/lib/utils/typeUtils.test.ts | 16 ++--- src/lib/utils/typeUtils.ts | 12 ++-- 7 files changed, 85 insertions(+), 54 deletions(-) diff --git a/src/lib/components/__snapshots__/JSONEditor.test.ts.snap b/src/lib/components/__snapshots__/JSONEditor.test.ts.snap index 12bc6944..85d98fb7 100644 --- a/src/lib/components/__snapshots__/JSONEditor.test.ts.snap +++ b/src/lib/components/__snapshots__/JSONEditor.test.ts.snap @@ -7,7 +7,7 @@ exports[`JSONEditor > render table mode 1`] = ` class="jse-main svelte-57bmz4" >
render table mode 1`] = `
0
render table mode 1`] = `
render table mode 1`] = `
1
render table mode 1`] = `
render table mode 1`] = `
2
render table mode 1`] = `
render table mode 1`] = ` class="jse-table-invisible-end-section" >
diff --git a/src/lib/logic/documentState.test.ts b/src/lib/logic/documentState.test.ts index c9af9bcf..0607bd56 100644 --- a/src/lib/logic/documentState.test.ts +++ b/src/lib/logic/documentState.test.ts @@ -35,6 +35,7 @@ import { type DocumentState, type ObjectDocumentState, type OnExpand, + type ValueDocumentState, type VisibleSection } from '$lib/types.js' import { deleteIn, getIn, type JSONPatchDocument, setIn, updateIn } from 'immutable-json-patch' @@ -859,14 +860,36 @@ describe('documentState', () => { ) }) - test('should keep/update enforce string', () => { + test('should determine enforce string', () => { const json1 = 42 const documentState1 = createDocumentState({ json: json1 }) - assert.strictEqual(getEnforceString(json1, documentState1, [], JSON), false) + assert.strictEqual(getEnforceString(json1, documentState1, []), false) const json2 = '42' const documentState2 = createDocumentState({ json: json2 }) - assert.strictEqual(getEnforceString(json2, documentState2, [], JSON), true) + assert.strictEqual(getEnforceString(json2, documentState2, []), true) + + const json3 = 'true' + const documentState3 = createDocumentState({ json: json3 }) + assert.strictEqual(getEnforceString(json3, documentState3, []), true) + + const json4 = 'null' + const documentState4 = createDocumentState({ json: json4 }) + assert.strictEqual(getEnforceString(json4, documentState4, []), true) + }) + + test('should create enforce string state if needed', () => { + const json = '42' + const documentState = createDocumentState({ json }) + assert.strictEqual(getEnforceString(json, documentState, []), true) + assert.strictEqual((documentState as ValueDocumentState).enforceString, undefined) + + const result = documentStatePatch(json, documentState, [ + { op: 'replace', path: '', value: 'abc' } + ]) + assert.strictEqual(result.json, 'abc') + assert.strictEqual(getEnforceString(result.json, result.documentState, []), true) + assert.strictEqual((result.documentState as ValueDocumentState).enforceString, true) }) describe('documentStatePatch', () => { diff --git a/src/lib/logic/documentState.ts b/src/lib/logic/documentState.ts index b82fe93d..b1b06f74 100644 --- a/src/lib/logic/documentState.ts +++ b/src/lib/logic/documentState.ts @@ -36,7 +36,6 @@ import type { ArrayDocumentState, CaretPosition, DocumentState, - JSONParser, ObjectDocumentState, OnExpand, ArrayRecursiveState, @@ -485,6 +484,14 @@ function _documentStatePatch( } if (isJSONPatchReplace(operation)) { + const path = parsePath(json, operation.path) + const enforceString = getEnforceString(json, documentState, path) + if (enforceString) { + // ensure the enforceString setting is not lost when for example changing "123" + // into "abc" and later back to "123", so we now make it explicit. + return setInDocumentState(json, documentState, path, { type: 'value', enforceString }) + } + // nothing special to do (all is handled by syncDocumentState) return documentState } @@ -701,8 +708,7 @@ function mergeAdjacentSections(visibleSections: VisibleSection[]): VisibleSectio export function getEnforceString( json: unknown, documentState: DocumentState | undefined, - path: JSONPath, - parser: JSONParser + path: JSONPath ): boolean { const value = getIn(json, path) const nestedState = getInRecursiveState(json, documentState, path) @@ -712,7 +718,7 @@ export function getEnforceString( return enforceString } - return isStringContainingPrimitiveValue(value, parser) + return isStringContainingPrimitiveValue(value) } export function getNextKeys(keys: string[], key: string, includeKey = false): string[] { diff --git a/src/lib/logic/table.ts b/src/lib/logic/table.ts index 0766ce93..1f41cefd 100644 --- a/src/lib/logic/table.ts +++ b/src/lib/logic/table.ts @@ -9,7 +9,7 @@ import { groupBy, isEmpty, isEqual, mapValues, partition } from 'lodash-es' import type { JSONSelection, SortedColumn, TableCellIndex, ValidationError } from '$lib/types.js' import { ValidationSeverity } from '$lib/types.js' import { createValueSelection, getFocusPath, pathStartsWith } from './selection.js' -import { isNumber } from '../utils/numberUtils.js' +import { containsNumber } from '../utils/numberUtils.js' import type { Dictionary } from 'lodash' import { stringifyJSONPath } from '$lib/utils/pathUtils.js' import { forEachSample } from '$lib/utils/arrayUtils.js' @@ -356,7 +356,7 @@ export function groupValidationErrors( columns: JSONPath[] ): GroupedValidationErrors { const [arrayErrors, rootErrors] = partition(validationErrors, (validationError) => - isNumber(validationError.path[0]) + containsNumber(validationError.path[0]) ) const errorsByRow: Dictionary = groupBy(arrayErrors, findRowIndex) diff --git a/src/lib/utils/numberUtils.ts b/src/lib/utils/numberUtils.ts index 0abb2008..c9b5b6ab 100644 --- a/src/lib/utils/numberUtils.ts +++ b/src/lib/utils/numberUtils.ts @@ -9,7 +9,7 @@ export function isDigit(char: string): boolean { const DIGIT_REGEX = /^[0-9]$/ // TODO: unit test -export function isNumber(value: string): boolean { +export function containsNumber(value: string): boolean { return NUMBER_REGEX.test(value) } diff --git a/src/lib/utils/typeUtils.test.ts b/src/lib/utils/typeUtils.test.ts index 85c610b3..9be54e26 100644 --- a/src/lib/utils/typeUtils.test.ts +++ b/src/lib/utils/typeUtils.test.ts @@ -108,14 +108,14 @@ describe('typeUtils', () => { }) test('isStringContainingPrimitiveValue', () => { - strictEqual(isStringContainingPrimitiveValue(22, JSON), false) - strictEqual(isStringContainingPrimitiveValue('text', JSON), false) - strictEqual(isStringContainingPrimitiveValue('2.4', JSON), true) - strictEqual(isStringContainingPrimitiveValue('-2.4', JSON), true) - strictEqual(isStringContainingPrimitiveValue('2e3', JSON), true) - strictEqual(isStringContainingPrimitiveValue('true', JSON), true) - strictEqual(isStringContainingPrimitiveValue('false', JSON), true) - strictEqual(isStringContainingPrimitiveValue('null', JSON), true) + strictEqual(isStringContainingPrimitiveValue(22), false) + strictEqual(isStringContainingPrimitiveValue('text'), false) + strictEqual(isStringContainingPrimitiveValue('2.4'), true) + strictEqual(isStringContainingPrimitiveValue('-2.4'), true) + strictEqual(isStringContainingPrimitiveValue('2e3'), true) + strictEqual(isStringContainingPrimitiveValue('true'), true) + strictEqual(isStringContainingPrimitiveValue('false'), true) + strictEqual(isStringContainingPrimitiveValue('null'), true) }) test('isInteger', () => { diff --git a/src/lib/utils/typeUtils.ts b/src/lib/utils/typeUtils.ts index 4eed9c22..c640bcff 100644 --- a/src/lib/utils/typeUtils.ts +++ b/src/lib/utils/typeUtils.ts @@ -1,6 +1,6 @@ // TODO: unit test typeUtils.js -import { isNumber } from './numberUtils.js' +import { containsNumber } from './numberUtils.js' import type { JSONParser } from '../types.js' /** @@ -134,7 +134,7 @@ export function valueType(value: unknown, parser: JSONParser): string { // unknown type (like a LosslessNumber). Try out what stringfying results in const valueStr = parser.stringify(value) - if (valueStr && isNumber(valueStr)) { + if (valueStr && containsNumber(valueStr)) { return 'number' } if (valueStr === 'true' || valueStr === 'false') { @@ -179,7 +179,7 @@ export function stringConvert(str: string, parser: JSONParser): unknown { return false } - if (isNumber(strTrim)) { + if (containsNumber(strTrim)) { return parser.parse(strTrim) } @@ -190,8 +190,10 @@ export function stringConvert(str: string, parser: JSONParser): unknown { * Test whether a string contains a numeric, boolean, or null value. * Returns true when the string contains a number, boolean, or null. */ -export function isStringContainingPrimitiveValue(str: unknown, parser: JSONParser): boolean { - return typeof str === 'string' && typeof stringConvert(str, parser) !== 'string' +export function isStringContainingPrimitiveValue(str: unknown): boolean { + // note that we can safely use JSON parser here instead of the configured JSONParser, + // since we do not actually use the parsed number, just want to check that it is not a string + return typeof str === 'string' && typeof stringConvert(str, JSON) !== 'string' } /**