This commit is contained in:
+4
-2
@@ -32,7 +32,9 @@ function atomicWriteFileSync(file, data) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
function writeJsonSync(file, obj) {
|
function writeJsonSync(file, obj) {
|
||||||
atomicWriteFileSync(file, JSON.stringify(obj, null, 2) + '\n');
|
const json = JSON.stringify(obj, null, 2);
|
||||||
|
if (json === undefined) throw new TypeError(`writeJsonSync: value for ${file} is not JSON-serializable`);
|
||||||
|
atomicWriteFileSync(file, json + '\n');
|
||||||
}
|
}
|
||||||
|
|
||||||
function readJsonSync(file) {
|
function readJsonSync(file) {
|
||||||
@@ -43,7 +45,7 @@ function readJsonIfExists(file, fallback) {
|
|||||||
try {
|
try {
|
||||||
return readJsonSync(file);
|
return readJsonSync(file);
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
if (err.code === 'ENOENT') return fallback;
|
if (err.code === 'ENOENT' || err instanceof SyntaxError) return fallback;
|
||||||
throw err;
|
throw err;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -78,6 +78,11 @@ Initial release.
|
|||||||
earlier) and use it for the click-marker placement, and the
|
earlier) and use it for the click-marker placement, and the
|
||||||
click-capture cache is armed as soon as recording starts so the very
|
click-capture cache is armed as soon as recording starts so the very
|
||||||
first click is captured instantly.
|
first click is captured instantly.
|
||||||
|
- Settings no longer fails to open if `app-settings.json` or
|
||||||
|
`placeholders.json` was previously corrupted (e.g. left containing the
|
||||||
|
literal text "undefined" by an old bug); a corrupted file is now
|
||||||
|
treated as empty instead of crashing the dialog, and is overwritten
|
||||||
|
with valid JSON the next time settings are saved.
|
||||||
|
|
||||||
### Added (initial feature set)
|
### Added (initial feature set)
|
||||||
|
|
||||||
|
|||||||
@@ -3,12 +3,16 @@
|
|||||||
const test = require('node:test');
|
const test = require('node:test');
|
||||||
const assert = require('node:assert/strict');
|
const assert = require('node:assert/strict');
|
||||||
|
|
||||||
|
const fs = require('node:fs');
|
||||||
|
const path = require('node:path');
|
||||||
|
|
||||||
const {
|
const {
|
||||||
systemPlaceholders, resolveScopes, expandPlaceholders,
|
systemPlaceholders, resolveScopes, expandPlaceholders,
|
||||||
listPlaceholders, collectGuidePlaceholders,
|
listPlaceholders, collectGuidePlaceholders,
|
||||||
} = require('../../core/placeholders');
|
} = require('../../core/placeholders');
|
||||||
const { createGuide, createStep } = require('../../core/schema');
|
const { createGuide, createStep } = require('../../core/schema');
|
||||||
const { Settings, DEFAULT_SETTINGS } = require('../../core/settings');
|
const { Settings, DEFAULT_SETTINGS } = require('../../core/settings');
|
||||||
|
const { writeJsonSync } = require('../../core/util');
|
||||||
const { makeTmpDir, rmrf } = require('./helpers');
|
const { makeTmpDir, rmrf } = require('./helpers');
|
||||||
|
|
||||||
test('placeholder expansion respects guide > global > system precedence', () => {
|
test('placeholder expansion respects guide > global > system precedence', () => {
|
||||||
@@ -61,3 +65,29 @@ test('settings persist, deep-merge with defaults, and store global placeholders'
|
|||||||
assert.equal(s2.get('capture.clickMarker'), DEFAULT_SETTINGS.capture.clickMarker);
|
assert.equal(s2.get('capture.clickMarker'), DEFAULT_SETTINGS.capture.clickMarker);
|
||||||
assert.deepEqual(s2.getGlobalPlaceholders(), { Company: 'Acme', Author: 'Tyler' });
|
assert.deepEqual(s2.getGlobalPlaceholders(), { Company: 'Acme', Author: 'Tyler' });
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('a corrupted placeholders/settings file falls back instead of crashing the settings dialog', (t) => {
|
||||||
|
const dir = makeTmpDir('settings-corrupt');
|
||||||
|
t.after(() => rmrf(dir));
|
||||||
|
|
||||||
|
// Simulate a file left over from a past bug: literal "undefined" instead of JSON.
|
||||||
|
fs.writeFileSync(path.join(dir, 'placeholders.json'), 'undefined\n');
|
||||||
|
fs.writeFileSync(path.join(dir, 'app-settings.json'), 'undefined\n');
|
||||||
|
|
||||||
|
const s = new Settings(dir);
|
||||||
|
assert.deepEqual(s.getGlobalPlaceholders(), {});
|
||||||
|
assert.equal(s.get('appearance'), DEFAULT_SETTINGS.appearance);
|
||||||
|
|
||||||
|
// Saving afterwards overwrites the corrupted file with valid JSON.
|
||||||
|
s.setGlobalPlaceholders({ Author: 'Tyler' });
|
||||||
|
assert.deepEqual(s.getGlobalPlaceholders(), { Author: 'Tyler' });
|
||||||
|
});
|
||||||
|
|
||||||
|
test('writeJsonSync refuses to write a non-JSON value instead of writing the literal string "undefined"', () => {
|
||||||
|
const dir = makeTmpDir('write-json-guard');
|
||||||
|
try {
|
||||||
|
assert.throws(() => writeJsonSync(path.join(dir, 'bad.json'), undefined), TypeError);
|
||||||
|
} finally {
|
||||||
|
rmrf(dir);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|||||||
Reference in New Issue
Block a user