Started Settings unit tests.
This commit is contained in:
parent
a30c0dd179
commit
72648f7430
3 changed files with 250 additions and 140 deletions
59
__mocks__/fs.js
Normal file
59
__mocks__/fs.js
Normal file
|
@ -0,0 +1,59 @@
|
|||
const fs = jest.createMockFromModule('fs');
|
||||
|
||||
let fileContents = '';
|
||||
|
||||
function __setFileContents(contents) {
|
||||
fileContents = contents;
|
||||
}
|
||||
|
||||
let writePromise = null;
|
||||
|
||||
function __setWritePromise(resolve) {
|
||||
writePromise = Promise.resolve(resolve);
|
||||
}
|
||||
|
||||
// example info from Node docs
|
||||
function statSync(file) {
|
||||
return {
|
||||
dev: 2114,
|
||||
ino: 48064969,
|
||||
mode: 33188,
|
||||
nlink: 1,
|
||||
uid: 85,
|
||||
gid: 100,
|
||||
rdev: 0,
|
||||
size: 527,
|
||||
blksize: 4096,
|
||||
blocks: 8,
|
||||
atimeMs: 1318289051000.1,
|
||||
mtimeMs: 1318289051000.1,
|
||||
ctimeMs: 1318289051000.1,
|
||||
birthtimeMs: 1318289051000.1,
|
||||
atime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
mtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
ctime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
birthtime: 'Mon, 10 Oct 2011 23:24:11 GMT',
|
||||
};
|
||||
}
|
||||
|
||||
function writeFileSync(file, contents) {
|
||||
|
||||
}
|
||||
|
||||
function readFileSync(file, options) {
|
||||
return fileContents;
|
||||
}
|
||||
|
||||
let promises = {
|
||||
writeFile: jest.fn(() => writePromise),
|
||||
readFile: jest.fn(() => fileContents),
|
||||
};
|
||||
|
||||
fs.__setFileContents = __setFileContents;
|
||||
fs.__setWritePromise = __setWritePromise;
|
||||
fs.statSync = statSync;
|
||||
fs.writeFileSync = writeFileSync;
|
||||
fs.readFileSync = readFileSync;
|
||||
fs.promises = promises;
|
||||
|
||||
module.exports = fs;
|
|
@ -1,171 +1,172 @@
|
|||
import { EliteMatrix } from "elite-matrix";
|
||||
import {EliteMatrix} from 'elite-matrix';
|
||||
import {PathLike} from 'fs';
|
||||
|
||||
const EventEmitter = require('node:events');
|
||||
const fs = require('node:fs/promises');
|
||||
const { statSync, writeFileSync, readFileSync } = require('node:fs');
|
||||
// Jest can't parse 'node:fs' so this has to be 'fs' for testing.
|
||||
const fs = require('fs/promises');
|
||||
const {statSync, writeFileSync, readFileSync} = require('fs');
|
||||
const ini = require('ini');
|
||||
const os = require('node:os');
|
||||
const path = require('node:path');
|
||||
const { setTimeout } = require('node:timers/promises');
|
||||
const {setTimeout} = require('node:timers/promises');
|
||||
const xmlJS = require('xml-js');
|
||||
|
||||
import { Log } from "./Log";
|
||||
import {Log} from './Log';
|
||||
|
||||
interface settingsFile {
|
||||
minValue: number,
|
||||
maxDistance: number,
|
||||
matrixFile: string,
|
||||
minValue: number,
|
||||
maxDistance: number,
|
||||
matrixFile: string,
|
||||
}
|
||||
|
||||
export class Settings extends EventEmitter {
|
||||
static #instance: Settings;
|
||||
static #instance: Settings;
|
||||
|
||||
#file: string;
|
||||
#writing: boolean;
|
||||
readonly #file: string;
|
||||
#writing: boolean;
|
||||
|
||||
minValue: number;
|
||||
maxDistance: number;
|
||||
minValue: number;
|
||||
maxDistance: number;
|
||||
|
||||
#matrixFile: null|string;
|
||||
matrix?: EliteMatrix;
|
||||
#matrixFile: null | string;
|
||||
matrix?: EliteMatrix;
|
||||
|
||||
private constructor(isPackaged: boolean) {
|
||||
super();
|
||||
private constructor(isPackaged: boolean) {
|
||||
super();
|
||||
|
||||
if (!isPackaged && os.platform() === 'linux') {
|
||||
this.#file = '/mnt/c/Users/marle/ed-safari-settings.json';
|
||||
} else {
|
||||
this.#file = path.join(os.homedir(), 'ed-safari-settings.json');
|
||||
}
|
||||
|
||||
// Check if settings file exists, and create it if not. Using sync since it's such a small
|
||||
// file, and this information is neccesary to build the UI.
|
||||
try {
|
||||
statSync(this.#file);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
const contents: string = JSON.stringify({
|
||||
minValue: 500000,
|
||||
maxDistance: 10000,
|
||||
matrixFile: '',
|
||||
});
|
||||
|
||||
writeFileSync(this.#file, contents);
|
||||
}
|
||||
}
|
||||
|
||||
// Initial reading of settings file done in sync for same reasons as above.
|
||||
const contents: settingsFile = JSON.parse(readFileSync(this.#file, { encoding: 'utf8' }));
|
||||
this.minValue = contents.minValue;
|
||||
this.maxDistance = contents.maxDistance;
|
||||
this.#matrixFile = contents.matrixFile;
|
||||
this.#writing = false;
|
||||
|
||||
if (this.#matrixFile) {
|
||||
this.#setMatrix();
|
||||
}
|
||||
if (!isPackaged && os.platform() === 'linux') {
|
||||
this.#file = '/mnt/c/Users/marle/ed-safari-settings.json';
|
||||
} else {
|
||||
this.#file = path.join(os.homedir(), 'ed-safari-settings.json');
|
||||
}
|
||||
|
||||
static get(isPackaged: boolean = false): Settings {
|
||||
if (!Settings.#instance) {
|
||||
Settings.#instance = new Settings(isPackaged);
|
||||
}
|
||||
// Check if settings file exists, and create it if not. Using sync since it's such a small
|
||||
// file, and this information is necessary to build the UI.
|
||||
try {
|
||||
statSync(this.#file);
|
||||
} catch (err) {
|
||||
if (err.code === 'ENOENT') {
|
||||
const contents: string = JSON.stringify({
|
||||
minValue: 500000,
|
||||
maxDistance: 10000,
|
||||
matrixFile: '',
|
||||
});
|
||||
|
||||
return Settings.#instance;
|
||||
writeFileSync(this.#file, contents);
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------------- save ---- */
|
||||
// Initial reading of settings file done in sync for same reasons as above.
|
||||
const contents: settingsFile = JSON.parse(readFileSync(this.#file, {encoding: 'utf8'}));
|
||||
this.minValue = contents.minValue;
|
||||
this.maxDistance = contents.maxDistance;
|
||||
this.#matrixFile = contents.matrixFile;
|
||||
this.#writing = false;
|
||||
|
||||
async save(settings: settingsFile): Promise<boolean> {
|
||||
if (!this.#writing) {
|
||||
try {
|
||||
Log.write('Attempting to save changed settings...');
|
||||
if (this.#matrixFile) {
|
||||
this.#setMatrix();
|
||||
}
|
||||
}
|
||||
|
||||
// So we don't try to write again before this one finishes.
|
||||
this.#writing = true;
|
||||
await fs.writeFile(this.#file, JSON.stringify(settings));
|
||||
this.#writing = false;
|
||||
|
||||
Log.write('Settings saved!');
|
||||
|
||||
// Update Settings props.
|
||||
await this.#read();
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
Log.write(err);
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
static get(isPackaged: boolean = false): Settings {
|
||||
if (!Settings.#instance) {
|
||||
Settings.#instance = new Settings(isPackaged);
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- #read ---- */
|
||||
return Settings.#instance;
|
||||
}
|
||||
|
||||
async #read(): Promise<boolean> {
|
||||
try {
|
||||
const file: string = await fs.readFile(this.#file, { encoding: 'utf8' });
|
||||
const contents: settingsFile = JSON.parse(file);
|
||||
/* -------------------------------------------------------------------------------- save ---- */
|
||||
|
||||
this.minValue = contents.minValue;
|
||||
this.maxDistance = contents.maxDistance;
|
||||
this.#matrixFile = contents.matrixFile;
|
||||
async save(settings: settingsFile): Promise<boolean> {
|
||||
try {
|
||||
Log.write('Attempting to save changed settings...');
|
||||
|
||||
if (this.#matrixFile) {
|
||||
await this.#setMatrix();
|
||||
Log.write('Custom colors set!');
|
||||
}
|
||||
await fs.writeFile(this.#file, JSON.stringify(settings));
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
Log.write(err);
|
||||
return false;
|
||||
}
|
||||
Log.write('Settings saved!');
|
||||
|
||||
// Update Settings props.
|
||||
await this.#read();
|
||||
|
||||
return true;
|
||||
|
||||
} catch (err) {
|
||||
Log.write(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------------- #read ---- */
|
||||
|
||||
async #read(): Promise<boolean> {
|
||||
try {
|
||||
const file: string = await fs.readFile(this.#file, {encoding: 'utf8'});
|
||||
const contents: settingsFile = JSON.parse(file);
|
||||
|
||||
this.minValue = contents.minValue;
|
||||
this.maxDistance = contents.maxDistance;
|
||||
this.#matrixFile = contents.matrixFile;
|
||||
|
||||
if (this.#matrixFile) {
|
||||
await this.#setMatrix();
|
||||
Log.write('Custom colors set!');
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (err) {
|
||||
Log.write(err);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- #setMatrix ---- */
|
||||
|
||||
async #setMatrix(): Promise<void> {
|
||||
const file: string = await fs.readFile((
|
||||
this.#matrixFile as PathLike
|
||||
), {encoding: 'utf8'});
|
||||
|
||||
let matrixRed: [number, number, number];
|
||||
let matrixGreen: [number, number, number];
|
||||
let matrixBlue: [number, number, number];
|
||||
|
||||
if (this.#matrixFile && path.basename(this.#matrixFile) === 'GraphicsConfiguration.xml') {
|
||||
const options = {
|
||||
trim: true,
|
||||
ignoreDeclaration: true,
|
||||
ignoreAttributes: true,
|
||||
compact: true,
|
||||
textKey: '$',
|
||||
};
|
||||
const contents = xmlJS.xml2js(file, options);
|
||||
|
||||
let matrix = [
|
||||
contents.GraphicsConfig.GUIColour.Default.MatrixRed.$,
|
||||
contents.GraphicsConfig.GUIColour.Default.MatrixGreen.$,
|
||||
contents.GraphicsConfig.GUIColour.Default.MatrixBlue.$,
|
||||
];
|
||||
|
||||
matrix = matrix.map(v => v.replace(/\s/g, '').split(','));
|
||||
|
||||
matrixRed = matrix[0].length === 3 ? matrix[0] : [1, 0, 0];
|
||||
matrixGreen = matrix[1].length === 3 ? matrix[1] : [0, 1, 0];
|
||||
matrixBlue = matrix[2].length === 3 ? matrix[2] : [0, 0, 1];
|
||||
|
||||
this.matrix = new EliteMatrix(matrixRed, matrixGreen, matrixBlue);
|
||||
|
||||
} else if (this.#matrixFile && path.basename(this.#matrixFile) === 'XML-Profile.ini') {
|
||||
const contents = (
|
||||
ini.parse(file)
|
||||
).constants;
|
||||
|
||||
matrixRed = [contents.x150, contents.y150, contents.z150];
|
||||
matrixGreen = [contents.x151, contents.y151, contents.z151];
|
||||
matrixBlue = [contents.x152, contents.y152, contents.z152];
|
||||
|
||||
this.matrix = new EliteMatrix(matrixRed, matrixGreen, matrixBlue);
|
||||
}
|
||||
|
||||
/* -------------------------------------------------------------------------- #setMatrix ---- */
|
||||
|
||||
async #setMatrix(): Promise<void> {
|
||||
const file: string = await fs.readFile(this.#matrixFile, { encoding: 'utf8' });
|
||||
|
||||
let matrixRed: [number, number, number];
|
||||
let matrixGreen: [number, number, number];
|
||||
let matrixBlue: [number, number, number];
|
||||
|
||||
if (path.basename(this.#matrixFile) === 'GraphicsConfiguration.xml') {
|
||||
const options = {
|
||||
trim: true,
|
||||
ignoreDeclaration: true,
|
||||
ignoreAttributes: true,
|
||||
compact: true,
|
||||
textKey: '$'
|
||||
};
|
||||
const contents = xmlJS.xml2js(file, options);
|
||||
|
||||
let matrix = [
|
||||
contents.GraphicsConfig.GUIColour.Default.MatrixRed.$,
|
||||
contents.GraphicsConfig.GUIColour.Default.MatrixGreen.$,
|
||||
contents.GraphicsConfig.GUIColour.Default.MatrixBlue.$,
|
||||
];
|
||||
|
||||
matrix = matrix.map(v => v.replace(/\s/g, '').split(','));
|
||||
|
||||
matrixRed = matrix[0].length === 3 ? matrix[0] : [1,0,0];
|
||||
matrixGreen = matrix[1].length === 3 ? matrix[1] : [0,1,0];
|
||||
matrixBlue = matrix[2].length === 3 ? matrix[2] : [0,0,1];
|
||||
|
||||
this.matrix = new EliteMatrix(matrixRed, matrixGreen, matrixBlue);
|
||||
|
||||
} else if (path.basename(this.#matrixFile) === 'XML-Profile.ini') {
|
||||
const contents = (ini.parse(file)).constants;
|
||||
|
||||
matrixRed = [contents.x150, contents.y150, contents.z150];
|
||||
matrixGreen = [contents.x151, contents.y151, contents.z151];
|
||||
matrixBlue = [contents.x152, contents.y152, contents.z152];
|
||||
|
||||
this.matrix = new EliteMatrix(matrixRed, matrixGreen, matrixBlue);
|
||||
}
|
||||
|
||||
this.emit('CUSTOM_COLORS_SET');
|
||||
}
|
||||
}
|
||||
this.emit('CUSTOM_COLORS_SET');
|
||||
}
|
||||
}
|
||||
|
|
50
test/Settings.test.ts
Normal file
50
test/Settings.test.ts
Normal file
|
@ -0,0 +1,50 @@
|
|||
import {expect} from '@jest/globals';
|
||||
import {Settings} from '../src/models/Settings';
|
||||
|
||||
jest.mock('fs');
|
||||
|
||||
describe('Settings', () => {
|
||||
const settingsFile = {
|
||||
minValue: 500000,
|
||||
maxDistance: 10000,
|
||||
matrixFile: '',
|
||||
};
|
||||
|
||||
beforeEach(() => {
|
||||
require('fs').__setFileContents(JSON.stringify(settingsFile));
|
||||
});
|
||||
|
||||
describe('get()', () => {
|
||||
it('should get instance', () => {
|
||||
expect(Settings.get()).toBeInstanceOf(Settings);
|
||||
});
|
||||
|
||||
it('should set initial values', () => {
|
||||
const minValue = Settings.get().minValue;
|
||||
expect(minValue).toBeDefined();
|
||||
expect(typeof minValue).toBe('number');
|
||||
|
||||
const maxDistance = Settings.get().maxDistance;
|
||||
expect(maxDistance).toBeDefined();
|
||||
expect(typeof maxDistance).toBe('number');
|
||||
|
||||
const matrix = Settings.get().matrix;
|
||||
expect(matrix).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
describe('save()', () => {
|
||||
it('should return boolean', async () => {
|
||||
const result = await Settings.get().save(settingsFile);
|
||||
expect(typeof result).toBe('boolean');
|
||||
|
||||
require('fs').__setWritePromise(true);
|
||||
const resultResolve = await Settings.get().save(settingsFile);
|
||||
expect(resultResolve).toBe(true);
|
||||
|
||||
require('fs').__setWritePromise(false);
|
||||
const resultReject = await Settings.get().save(settingsFile);
|
||||
expect(resultReject).toBe(false);
|
||||
});
|
||||
});
|
||||
});
|
Loading…
Reference in a new issue