1.1.0 - The package has been migrated to TypeScript. Unit tests using Jest have also been added.

master
Tudor Stanciu 2023-03-30 19:07:09 +03:00
parent a73cd2a491
commit ea62905a56
19 changed files with 7044 additions and 2287 deletions

3
.gitignore vendored
View File

@ -6,6 +6,7 @@ node_modules
# builds # builds
build build
lib
dist dist
.rpt2_cache .rpt2_cache
@ -21,3 +22,5 @@ npm-debug.log*
yarn-debug.log* yarn-debug.log*
yarn-error.log* yarn-error.log*
example/package-lock.json example/package-lock.json
coverage

View File

@ -1,4 +0,0 @@
#!/bin/sh
. "$(dirname "$0")/_/husky.sh"
npm run precommit

22
LICENSE Normal file
View File

@ -0,0 +1,22 @@
MIT License
Copyright (c) 2023 Tudor Stanciu
Permission is hereby granted, free of charge, to any person obtaining
a copy of this software and associated documentation files (the
"Software"), to deal in the Software without restriction, including
without limitation the rights to use, copy, modify, merge, publish,
distribute, sublicense, and/or sell copies of the Software, and to
permit persons to whom the Software is furnished to do so, subject to
the following conditions:
The above copyright notice and this permission notice shall be
included in all copies or substantial portions of the Software.
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

View File

@ -14,12 +14,18 @@ npm i --save @flare/js-utils --registry https://lab.code-rove.com/public-node-re
yarn add @flare/js-utils --registry https://lab.code-rove.com/public-node-registry yarn add @flare/js-utils --registry https://lab.code-rove.com/public-node-registry
``` ```
## Usage ## How to use the package
```jsx ```jsx
import { typeValidator, localStorage, camelizeKeys } from "@flare/js-utils"; import { typeValidator, localStorage, camelizeKeys } from "@flare/js-utils";
``` ```
## Unit testing
Unit testing is done using [Jest](https://jestjs.io/). This is an awesome testing framework created by Facebook.
The files containing tests are identified by the extension `*.test.ts`.
All tests in the package can be executed by running: `npm test`.
## Changelog ## Changelog
1.0.0 - This version includes data type validators and local storage utils 1.0.0 - This version includes data type validators and local storage utils
@ -27,3 +33,4 @@ import { typeValidator, localStorage, camelizeKeys } from "@flare/js-utils";
1.0.2 - Utility functions export fix 1.0.2 - Utility functions export fix
1.0.3 - Typescript warnings fix 1.0.3 - Typescript warnings fix
1.0.4 - Added "camelizeKeys" function 1.0.4 - Added "camelizeKeys" function
1.1.0 - The package has been migrated to TypeScript. Unit tests using Jest have also been added.

17
jestconfig.json Normal file
View File

@ -0,0 +1,17 @@
{
"transform": {
"^.+\\.(t|j)sx?$": "ts-jest"
},
"testRegex": "(/__tests__/.*|(\\.|/)(test|spec))\\.(jsx?|tsx?)$",
"moduleFileExtensions": ["ts", "tsx", "js", "jsx", "json", "node"],
"collectCoverage": true,
"coverageThreshold": {
"global": {
"branches": 50,
"functions": 50,
"lines": 50,
"statements": 50
}
}
}

8902
package-lock.json generated

File diff suppressed because it is too large Load Diff

View File

@ -1,7 +1,26 @@
{ {
"name": "@flare/js-utils", "name": "@flare/js-utils",
"version": "1.0.4", "version": "1.1.0",
"description": "Javascript utils", "description": "js-utils is a collection of utilities that facilitate software development in a javascript environment.",
"main": "./lib/index.js",
"types": "./lib/index.d.ts",
"scripts": {
"build": "tsc",
"test": "jest --config jestconfig.json",
"format": "prettier --write \"src/**/*.ts\"",
"lint": "tslint -p tsconfig.json",
"prepublishOnly": "npm test && npm run lint",
"prepush": "npm install && npm run build",
"preversion": "npm run lint",
"version": "npm run format && git add -A src",
"push": "npm publish",
"push:major": "npm run version:major && npm run push",
"push:minor": "npm run version:minor && npm run push",
"push:patch": "npm run version:patch && npm run push",
"version:major": "npm version major --no-git-tag-version",
"version:minor": "npm version minor --no-git-tag-version",
"version:patch": "npm version patch --no-git-tag-version"
},
"author": { "author": {
"name": "Tudor Stanciu", "name": "Tudor Stanciu",
"email": "tudor.stanciu94@gmail.com", "email": "tudor.stanciu94@gmail.com",
@ -16,39 +35,23 @@
"flare", "flare",
"js-utils" "js-utils"
], ],
"main": "./src/index.js", "bugs": {
"babel": { "url": "https://lab.code-rove.com/gitea/bricks/js-utils/issues"
"presets": [
"@babel/preset-env"
]
},
"scripts": {
"prebuild": "rimraf build",
"build": "npm run build:cjs && npm run build:copy-files",
"build:cjs": "babel src -d build --copy-files",
"build:copy-files": "node ./scripts/copy-files.js",
"precommit": "lint-staged",
"prepare": "husky install",
"test": "echo \"Error: no test specified\" && exit 1",
"prepush": "npm run build",
"push": "cd build && npm publish --registry https://lab.code-rove.com/public-node-registry",
"push:major": "npm run version:major && npm run push",
"push:minor": "npm run version:minor && npm run push",
"push:patch": "npm run version:patch && npm run push",
"version:major": "npm version major --no-git-tag-version",
"version:minor": "npm version minor --no-git-tag-version",
"version:patch": "npm version patch --no-git-tag-version"
}, },
"homepage": "https://lab.code-rove.com/gitea/bricks/js-utils#readme",
"files": [
"lib/**/*",
"LICENSE"
],
"devDependencies": { "devDependencies": {
"@babel/cli": "^7.16.7", "@types/jest": "^29.4.0",
"@babel/core": "^7.16.7", "jest": "^29.4.1",
"@babel/node": "^7.16.7", "jest-environment-jsdom": "^29.4.2",
"@babel/preset-env": "^7.16.7", "prettier": "^2.8.3",
"rimraf": "^3.0.2", "ts-jest": "^29.0.5",
"fs-extra": "^10.0.0", "tslint": "^6.1.3",
"husky": "^7.0.4", "tslint-config-prettier": "^1.18.0",
"lint-staged": "^12.2.2", "typescript": "^4.9.5"
"prettier": "^2.5.1"
}, },
"prettier": { "prettier": {
"trailingComma": "none", "trailingComma": "none",
@ -57,12 +60,6 @@
"singleQuote": false, "singleQuote": false,
"arrowParens": "avoid" "arrowParens": "avoid"
}, },
"lint-staged": {
"**/*.+(js|md|ts|css|sass|less|graphql|scss|json|vue)": [
"prettier --write",
"git add"
]
},
"publishConfig": { "publishConfig": {
"registry": "https://lab.code-rove.com/public-node-registry" "registry": "https://lab.code-rove.com/public-node-registry"
} }

View File

@ -1,105 +0,0 @@
/* eslint-disable no-console */
const path = require("path");
const fse = require("fs-extra");
const glob = require("glob");
const packagePath = process.cwd();
const buildPath = path.join(packagePath, "./build");
const srcPath = path.join(packagePath, "./src");
/**
* Puts a package.json into every immediate child directory of rootDir.
* That package.json contains information about esm for bundlers so that imports
* like import Typography from '@material-ui/core/Typography' are tree-shakeable.
*
* It also tests that an this import can be used in typescript by checking
* if an index.d.ts is present at that path.
*
* @param {string} rootDir
*/
// async function createModulePackages({ from, to }) {
// const directoryPackages = glob.sync('*/index.js', { cwd: from }).map(path.dirname);
// await Promise.all(
// directoryPackages.map(async directoryPackage => {
// const packageJson = {
// sideEffects: false,
// module: path.join('../esm', directoryPackage, 'index.js'),
// typings: './index.d.ts',
// };
// const packageJsonPath = path.join(to, directoryPackage, 'package.json');
// /*const [typingsExist] =*/ await Promise.all([
// //fse.exists(path.join(to, directoryPackage, 'index.d.ts')),
// fse.writeFile(packageJsonPath, JSON.stringify(packageJson, null, 2)),
// ]);
// // if (!typingsExist) {
// // throw new Error(`index.d.ts for ${directoryPackage} is missing`);
// // }
// return packageJsonPath;
// }),
// );
// }
async function typescriptCopy({ from, to }) {
if (!(await fse.exists(to))) {
console.warn(`path ${to} does not exists`);
return [];
}
const files = glob.sync("**/*.d.ts", { cwd: from });
const cmds = files.map(file =>
fse.copy(path.resolve(from, file), path.resolve(to, file))
);
return Promise.all(cmds);
}
async function createPackageFile() {
const packageData = await fse.readFile(
path.resolve(packagePath, "./package.json"),
"utf8"
);
// eslint-disable-next-line no-unused-vars
const { nyc, scripts, devDependencies, workspaces, ...packageDataOther } =
JSON.parse(packageData);
const newPackageData = {
...packageDataOther,
private: false,
main: "./index.js",
//module: "./esm/index.js",
typings: "./index.d.ts"
};
const targetPath = path.resolve(buildPath, "./package.json");
await fse.writeFile(
targetPath,
JSON.stringify(newPackageData, null, 2),
"utf8"
);
console.log(`Created package.json in ${targetPath}`);
fse.copy("./README.md", `${buildPath}/README.md`, err => {
if (err) throw err;
console.log("README file was copied to destination");
});
return newPackageData;
}
async function run() {
try {
await createPackageFile();
// TypeScript
await typescriptCopy({ from: srcPath, to: buildPath });
//await createModulePackages({ from: srcPath, to: buildPath });
} catch (err) {
console.error(err);
process.exit(1);
}
}
run();

View File

@ -0,0 +1,60 @@
// Copyright (c) 2023 Tudor Stanciu
import { camelizeKeys } from "../../data/camelizer";
describe("camelizeKeys", () => {
it("should camelize object keys", () => {
const obj = {
Test1: "value",
Test2: {
Value: "nested_value"
}
};
const expected = {
test1: "value",
test2: {
value: "nested_value"
}
};
const actual = camelizeKeys(obj);
expect(actual).toEqual(expected);
});
it("should not modify non-object values", () => {
const str = "test_string";
const num = 123;
expect(camelizeKeys(str)).toBe(str);
expect(camelizeKeys(num)).toBe(num);
});
it("should camelize object keys in an array", () => {
const arr = [
{
Test1: "value"
},
{
Test2: {
Value: "nested_value"
}
}
];
const expected = [
{
test1: "value"
},
{
test2: {
value: "nested_value"
}
}
];
const actual = camelizeKeys(arr);
expect(actual).toEqual(expected);
});
it("should handle null and undefined values", () => {
expect(camelizeKeys(null)).toBe(null);
expect(camelizeKeys(undefined)).toBe(undefined);
});
});

View File

@ -0,0 +1,46 @@
// Copyright (c) 2023 Tudor Stanciu
import { isArray, isObject, isJson } from "../../data/typeValidator";
describe("isArray", () => {
it("should return true when given an array", () => {
expect(isArray([])).toBe(true);
});
it("should return false when given a non-array", () => {
expect(isArray(123)).toBe(false);
expect(isArray("foo")).toBe(false);
expect(isArray({})).toBe(false);
expect(isArray(null)).toBe(false);
expect(isArray(undefined)).toBe(false);
});
});
describe("isObject", () => {
it("should return true when given an object", () => {
expect(isObject({})).toBe(true);
expect(isObject({ foo: "bar" })).toBe(true);
});
it("should return false when given a non-object", () => {
expect(isObject(123)).toBe(false);
expect(isObject("foo")).toBe(false);
expect(isObject([])).toBe(false);
expect(isObject(null)).toBe(false);
expect(isObject(undefined)).toBe(false);
});
});
describe("isJson", () => {
it("should return success: true and the parsed data when given valid JSON", () => {
const input = '{"foo": "bar"}';
const expectedOutput = { success: true, data: { foo: "bar" } };
expect(isJson(input)).toEqual(expectedOutput);
});
it("should return success: false and null when given invalid JSON", () => {
const input = '{foo: "bar"}';
const expectedOutput = { success: false, data: null };
expect(isJson(input)).toEqual(expectedOutput);
});
});

View File

@ -0,0 +1,79 @@
/**
* @jest-environment jsdom
*/
// Copyright (c) 2023 Tudor Stanciu
import {
setItem,
getItem,
removeItem,
clear,
getKey
} from "../../storage/localStorage";
describe("localStorage", () => {
beforeEach(() => {
window.localStorage.clear();
});
test("setItem should store a string value under a given key", () => {
setItem("myKey", "myValue");
expect(window.localStorage.getItem("myKey")).toBe("myValue");
});
test("setItem should store an object value under a given key", () => {
const obj = { foo: "bar" };
setItem("myKey", obj);
expect(JSON.parse(window.localStorage.getItem("myKey")!)).toEqual(obj);
});
test("setItem should store an array value under a given key", () => {
const arr = [1, 2, 3];
setItem("myKey", arr);
expect(JSON.parse(window.localStorage.getItem("myKey")!)).toEqual(arr);
});
test("getItem should retrieve a stored string value under a given key", () => {
window.localStorage.setItem("myKey", "myValue");
expect(getItem("myKey")).toBe("myValue");
});
test("getItem should retrieve a stored object value under a given key", () => {
const obj = { foo: "bar" };
window.localStorage.setItem("myKey", JSON.stringify(obj));
expect(getItem("myKey")).toEqual(obj);
});
test("getItem should retrieve a stored array value under a given key", () => {
const arr = [1, 2, 3];
window.localStorage.setItem("myKey", JSON.stringify(arr));
expect(getItem("myKey")).toEqual(arr);
});
test("getItem should return null if key does not exist", () => {
expect(getItem("nonexistentKey")).toBeNull();
});
test("removeItem should remove a value under a given key", () => {
window.localStorage.setItem("myKey", "myValue");
removeItem("myKey");
expect(window.localStorage.getItem("myKey")).toBeNull();
});
test("clear should remove all values", () => {
window.localStorage.setItem("myKey1", "myValue1");
window.localStorage.setItem("myKey2", "myValue2");
clear();
expect(window.localStorage.getItem("myKey1")).toBeNull();
expect(window.localStorage.getItem("myKey2")).toBeNull();
});
test("getKey should return the key at a given index", () => {
window.localStorage.setItem("myKey1", "myValue1");
window.localStorage.setItem("myKey2", "myValue2");
expect(getKey(0)).toBe("myKey1");
expect(getKey(1)).toBe("myKey2");
expect(getKey(2)).toBeNull();
});
});

View File

@ -1,5 +1,12 @@
function camelizeKeys(o) { // Copyright (c) 2023 Tudor Stanciu
var newO, origKey, newKey, value;
interface ObjectWithAnyKey {
[key: string]: any;
}
function camelizeKeys(o: any): any {
if (!o || (typeof o !== "object" && !Array.isArray(o))) return o;
let newO, value;
if (o instanceof Array) { if (o instanceof Array) {
return o.map(function (value) { return o.map(function (value) {
if (typeof value === "object") { if (typeof value === "object") {
@ -8,10 +15,10 @@ function camelizeKeys(o) {
return value; return value;
}); });
} else { } else {
newO = {}; newO = {} as ObjectWithAnyKey;
for (origKey in o) { for (const origKey in o) {
if (Object.prototype.hasOwnProperty.call(o, origKey)) { if (Object.prototype.hasOwnProperty.call(o, origKey)) {
newKey = ( const newKey = (
origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey origKey.charAt(0).toLowerCase() + origKey.slice(1) || origKey
).toString(); ).toString();
value = o[origKey]; value = o[origKey];

View File

@ -1,23 +0,0 @@
const isArray = parameter => {
const _isArray = parameter.constructor === Array;
// parameter instanceof Array;
// Array.isArray(parameter);
return _isArray;
};
const isObject = parameter => {
const _isObject = typeof parameter === "object" && parameter !== null;
return _isObject;
};
const isJson = str => {
try {
const data = JSON.parse(str);
return { data, success: true };
} catch (e) {
return { data: null, success: false };
}
};
export { isArray, isObject, isJson };

26
src/data/typeValidator.ts Normal file
View File

@ -0,0 +1,26 @@
// Copyright (c) 2023 Tudor Stanciu
const isArray = (parameter: any): boolean => {
const _isArray = parameter instanceof Array;
// Array.isArray(parameter);
return _isArray;
};
const isObject = (parameter: any): boolean => {
const _isObject =
typeof parameter === "object" &&
parameter !== null &&
!Array.isArray(parameter);
return _isObject;
};
const isJson = (text: string): { data: any; success: boolean } => {
try {
const data = JSON.parse(text);
return { data, success: true };
} catch (e) {
return { data: null, success: false };
}
};
export { isArray, isObject, isJson };

View File

@ -1,5 +0,0 @@
import * as typeValidator from "./data/typeValidator";
import * as localStorage from "./storage/localStorage";
import { camelizeKeys } from "./data/camelizer";
export { typeValidator, localStorage, camelizeKeys };

View File

@ -1,3 +1,5 @@
// Copyright (c) 2023 Tudor Stanciu
import * as typeValidator from "./data/typeValidator"; import * as typeValidator from "./data/typeValidator";
import * as localStorage from "./storage/localStorage"; import * as localStorage from "./storage/localStorage";
import { camelizeKeys } from "./data/camelizer"; import { camelizeKeys } from "./data/camelizer";

View File

@ -1,6 +1,8 @@
// Copyright (c) 2023 Tudor Stanciu
import { isArray, isObject, isJson } from "../data/typeValidator"; import { isArray, isObject, isJson } from "../data/typeValidator";
const setItem = (key, value) => { const setItem = (key: string, value: any): void => {
let valueToStore = value; let valueToStore = value;
if (isArray(value) || isObject(value)) { if (isArray(value) || isObject(value)) {
valueToStore = JSON.stringify(value); valueToStore = JSON.stringify(value);
@ -9,28 +11,27 @@ const setItem = (key, value) => {
window.localStorage.setItem(key, valueToStore); window.localStorage.setItem(key, valueToStore);
}; };
const getItem = key => { const getItem = (key: string): any => {
const value = window.localStorage.getItem(key); const value = window.localStorage.getItem(key);
if (value === null) return null;
const { data, success } = isJson(value); const { data, success } = isJson(value);
return success ? data : value;
if (success) {
return data;
} else {
return value;
}
}; };
const removeItem = key => { const removeItem = (key: string): void => {
window.localStorage.removeItem(key); window.localStorage.removeItem(key);
}; };
const clear = () => { const clear = (): void => {
window.localStorage.clear(); window.localStorage.clear();
}; };
const getKey = index => { const getKey = (index: number): string | null => {
const keyName = window.localStorage.key(index); const keyName = window.localStorage.key(index);
return keyName; return keyName;
}; };
const localStorage = { setItem, getItem, removeItem, clear, getKey };
export { setItem, getItem, removeItem, clear, getKey }; export { setItem, getItem, removeItem, clear, getKey };
export default localStorage;

19
tsconfig.json Normal file
View File

@ -0,0 +1,19 @@
{
"compilerOptions": {
"target": "es5",
"module": "commonjs",
"declaration": true,
"outDir": "./lib",
"jsx": "react",
"strict": true,
"esModuleInterop": true,
"skipLibCheck": true,
"forceConsistentCasingInFileNames": true,
"allowJs": false,
"noUnusedLocals": true,
"noUnusedParameters": true
},
"include": ["./src"],
"exclude": ["node_modules", "**/__tests__/*"]
}

4
tslint.json Normal file
View File

@ -0,0 +1,4 @@
{
"extends": ["tslint:recommended", "tslint-config-prettier"]
}