From 72648f743070fcf8269e519637d2d54c2efa5f31 Mon Sep 17 00:00:00 2001 From: marley Date: Wed, 17 May 2023 14:43:14 -0700 Subject: [PATCH] Started Settings unit tests. --- __mocks__/fs.js | 59 +++++++++ src/models/Settings.ts | 281 +++++++++++++++++++++-------------------- test/Settings.test.ts | 50 ++++++++ 3 files changed, 250 insertions(+), 140 deletions(-) create mode 100644 __mocks__/fs.js create mode 100644 test/Settings.test.ts diff --git a/__mocks__/fs.js b/__mocks__/fs.js new file mode 100644 index 0000000..c5bbe92 --- /dev/null +++ b/__mocks__/fs.js @@ -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; diff --git a/src/models/Settings.ts b/src/models/Settings.ts index a76d074..c7d68d9 100644 --- a/src/models/Settings.ts +++ b/src/models/Settings.ts @@ -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 { - 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 { - 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 { + 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 { + 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 { + 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 { - 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'); - } -} \ No newline at end of file + this.emit('CUSTOM_COLORS_SET'); + } +} diff --git a/test/Settings.test.ts b/test/Settings.test.ts new file mode 100644 index 0000000..2f2d0cd --- /dev/null +++ b/test/Settings.test.ts @@ -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); + }); + }); +});