diff --git a/packages/api/package.json b/packages/api/package.json index b0fda33d..8036d237 100644 --- a/packages/api/package.json +++ b/packages/api/package.json @@ -28,10 +28,10 @@ "lint": "eslint src/**/*" }, "devDependencies": { - "@agile-ts/core": "file:../core" + "@agile-ts/utils": "file:../utils" }, - "peerDependencies": { - "@agile-ts/core": "^0.0.14" + "dependencies": { + "@agile-ts/utils": "^0.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/api/src/index.ts b/packages/api/src/index.ts index 3112de9a..9c7880ec 100644 --- a/packages/api/src/index.ts +++ b/packages/api/src/index.ts @@ -1,4 +1,4 @@ -import { clone, copy, defineConfig, isValidObject } from '@agile-ts/core'; +import { clone, copy, defineConfig, isValidObject } from '@agile-ts/utils'; export default class API { public config: ApiConfig; diff --git a/packages/core/package.json b/packages/core/package.json index 8f4555b7..b654a72d 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -38,6 +38,14 @@ "test:coverage": "jest --coverage", "lint": "eslint src/**/*" }, + "devDependencies": { + "@agile-ts/logger": "file:../logger", + "@agile-ts/utils": "file:../utils" + }, + "dependencies": { + "@agile-ts/utils": "^0.0.1", + "@agile-ts/logger": "^0.0.1" + }, "publishConfig": { "access": "public" }, diff --git a/packages/core/src/internal.ts b/packages/core/src/internal.ts index 2be3650a..d0d7af8f 100644 --- a/packages/core/src/internal.ts +++ b/packages/core/src/internal.ts @@ -5,10 +5,11 @@ // !! All internal Agile modules must be imported from here!! // Logger -export * from './logger'; +export * from '@agile-ts/logger'; // Utils export * from './utils'; +export * from '@agile-ts/utils'; // Agile export * from './agile'; diff --git a/packages/core/src/utils.ts b/packages/core/src/utils.ts index 47aa119a..88e069e9 100644 --- a/packages/core/src/utils.ts +++ b/packages/core/src/utils.ts @@ -1,102 +1,13 @@ -import { Agile, Observer, Collection } from './internal'; +import { + Agile, + Observer, + Collection, + normalizeArray, + isFunction, +} from './internal'; //========================================================================================================= -// Copy -//========================================================================================================= -/** - * @internal - * Creates a fresh copy of an Array/Object - * https://www.samanthaming.com/tidbits/70-3-ways-to-clone-objects/ - * @param value - Array/Object that gets copied - */ -export function copy(value: T): T { - // Extra checking '!value' because 'typeof null === object' - if (!value) return value; - - // Ignore everything that is no object or array - const valConstructorName = Object.getPrototypeOf(value).constructor.name; - if (!['object', 'array'].includes(valConstructorName.toLowerCase())) - return value; - - let temp; - const newObject: any = Array.isArray(value) ? [] : {}; - for (const property in value) { - temp = value[property]; - newObject[property] = typeof temp === 'object' ? copy(temp) : temp; - } - return newObject as T; -} - -//========================================================================================================= -// Is Valid Object -//========================================================================================================= -/** - * @internal - * Checks if passed value is a valid Object - * https://stackoverflow.com/questions/12996871/why-does-typeof-array-with-objects-return-object-and-not-array - * @param value - Value that is tested for its correctness - * @param considerArray - Whether Arrays should be considered as object - */ -export function isValidObject(value: any, considerArray = false): boolean { - function isHTMLElement(obj: any) { - try { - return obj instanceof HTMLElement; - } catch (e) { - return ( - typeof obj === 'object' && - obj.nodeType === 1 && - typeof obj.style === 'object' && - typeof obj.ownerDocument === 'object' - ); - } - } - - return ( - value !== null && - typeof value === 'object' && - !isHTMLElement(value) && - (considerArray ? true : !Array.isArray(value)) - ); -} - -//========================================================================================================= -// Includes Array -//========================================================================================================= -/** - * @internal - * Check if array1 contains all elements of array2 - * @param array1 - Array 1 - * @param array2 - Array 2 - */ -export function includesArray( - array1: Array, - array2: Array -): boolean { - return array2.every((element) => array1.includes(element)); -} - -//========================================================================================================= -// Normalize Array -//========================================================================================================= -/** - * @internal - * Transforms Item/s to an Item Array - * @param items - Item/s that gets transformed to an Array - * @param config - Config - */ -export function normalizeArray( - items?: DataType | Array, - config: { createUndefinedArray?: boolean } = {} -): Array { - config = defineConfig(config, { - createUndefinedArray: false, // If it should return [] or [undefined] if the passed Item is undefined - }); - if (!items && !config.createUndefinedArray) return []; - return Array.isArray(items) ? items : [items as DataType]; -} - -//========================================================================================================= -// Get Instance +// Get Agile Instance //========================================================================================================= /** * @internal @@ -172,227 +83,6 @@ export function extractObservers(instances: any): Array { return instancesArray; } -//========================================================================================================= -// Is Function -//========================================================================================================= -/** - * @internal - * Checks if value is a function - * @param value - Value that gets tested if its a function - */ -export function isFunction(value: any): boolean { - return typeof value === 'function'; -} - -//========================================================================================================= -// Is Async Function -//========================================================================================================= -/** - * @internal - * Checks if value is an async function - * @param value - Value that gets tested if its an async function - */ -export function isAsyncFunction(value: any): boolean { - const valueString = value.toString(); - return ( - isFunction(value) && - (value.constructor.name === 'AsyncFunction' || - valueString.includes('__awaiter')) - ); -} - -//========================================================================================================= -// Is Json String -//========================================================================================================= -/** - * @internal - * Checks if value is valid JsonString - * @param value - Value that gets checked - */ -export function isJsonString(value: any): boolean { - if (typeof value !== 'string') return false; - try { - JSON.parse(value); - } catch (e) { - return false; - } - return true; -} - -//========================================================================================================= -// Define Config -//========================================================================================================= -/** - * @internal - * Merges default values/properties into config object - * @param config - Config object that receives default values - * @param defaults - Default values object that gets merged into config object - * @param overwriteUndefinedProperties - If undefined Properties in config gets overwritten by the default value - */ -export function defineConfig( - config: ConfigInterface, - defaults: Object, - overwriteUndefinedProperties?: boolean -): ConfigInterface { - if (overwriteUndefinedProperties === undefined) - overwriteUndefinedProperties = true; - - if (overwriteUndefinedProperties) { - const finalConfig = { ...defaults, ...config }; - for (const key in finalConfig) - if (finalConfig[key] === undefined) finalConfig[key] = defaults[key]; - return finalConfig; - } - - return { ...defaults, ...config }; -} - -//========================================================================================================= -// Flat Merge -//========================================================================================================= -/** - * @internal - * @param addNewProperties - Adds new properties to source Object - */ -export interface FlatMergeConfigInterface { - addNewProperties?: boolean; -} - -/** - * @internal - * Merges items into object, be aware that the merge will only happen at the top level of the object. - * Initially it adds new properties of the changes object into the source object. - * @param source - Source object - * @param changes - Changes that get merged into the source object - * @param config - Config - */ -export function flatMerge( - source: DataType, - changes: Object, - config: FlatMergeConfigInterface = {} -): DataType { - config = defineConfig(config, { - addNewProperties: true, - }); - - // Copy Source to avoid References - const _source = copy(source); - if (!_source) return _source; - - // Merge Changes Object into Source Object - const keys = Object.keys(changes); - keys.forEach((property) => { - if (!config.addNewProperties && !_source[property]) return; - _source[property] = changes[property]; - }); - - return _source; -} - -//========================================================================================================= -// Equals -//========================================================================================================= -/** - * @internal - * Check if two values are equal - * @param value1 - First Value - * @param value2 - Second Value - */ -export function equal(value1: any, value2: any): boolean { - return value1 === value2 || JSON.stringify(value1) === JSON.stringify(value2); -} - -//========================================================================================================= -// Not Equals -//========================================================================================================= -/** - * @internal - * Checks if two values aren't equal - * @param value1 - First Value - * @param value2 - Second Value - */ -export function notEqual(value1: any, value2: any): boolean { - return !equal(value1, value2); -} - -//========================================================================================================= -// Generate Id -//========================================================================================================= -/** - * @internal - * Generates random Id - * @param length - Length of generated Id - */ -export function generateId(length?: number): string { - const characters = - 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; - const charactersLength = characters.length; - let result = ''; - if (!length) length = 5; - for (let i = 0; i < length; i++) { - result += characters.charAt(Math.floor(Math.random() * charactersLength)); - } - return result; -} - -//========================================================================================================= -// Create Array From Object -//========================================================================================================= -/** - * @internal - * Transforms Object to Array - * @param object - Object that gets transformed - */ -export function createArrayFromObject

(object: { - [key: string]: P; -}): Array<{ key: string; instance: P }> { - const array: Array<{ key: string; instance: P }> = []; - for (const key in object) { - array.push({ - key: key, - instance: object[key], - }); - } - return array; -} - -//========================================================================================================= -// Clone -//========================================================================================================= -/** - * @internal - * Clones a Class - * @param instance - Instance of Class you want to clone - */ -export function clone(instance: T): T { - // Clone Class - const objectCopy: T = Object.create(Object.getPrototypeOf(instance)); - const objectClone = Object.assign(objectCopy, instance); - - // Copy Properties of Class to remove flat references - for (const key in objectClone) objectClone[key] = copy(objectClone[key]); - - return objectClone; -} - -//========================================================================================================= -// Remove Properties -//========================================================================================================= -/** - * @internal - * Removes properties from Object - * @param object - Object from which the properties get removed - * @param properties - Properties that get removed from the object - */ -export function removeProperties( - object: T, - properties: Array -): T { - const copiedObject = copy(object); - properties.map((property) => delete copiedObject[property]); - return copiedObject; -} - //========================================================================================================= // Global Bind //========================================================================================================= diff --git a/packages/core/tests/unit/collection/collection.test.ts b/packages/core/tests/unit/collection/collection.test.ts index edee560a..1bb68db5 100644 --- a/packages/core/tests/unit/collection/collection.test.ts +++ b/packages/core/tests/unit/collection/collection.test.ts @@ -8,7 +8,7 @@ import { ComputedTracker, StatePersistent, } from '../../../src'; -import * as Utils from '../../../src/utils'; +import * as Utils from '@agile-ts/utils'; import mockConsole from 'jest-mock-console'; jest.mock('../../../src/collection/collection.persistent'); diff --git a/packages/core/tests/unit/runtime/runtime.test.ts b/packages/core/tests/unit/runtime/runtime.test.ts index 001d1bac..7e58ff0f 100644 --- a/packages/core/tests/unit/runtime/runtime.test.ts +++ b/packages/core/tests/unit/runtime/runtime.test.ts @@ -7,7 +7,7 @@ import { Runtime, SubscriptionContainer, } from '../../../src'; -import * as Utils from '../../../src/utils'; +import * as Utils from '@agile-ts/utils'; import testIntegration from '../../helper/test.integration'; import mockConsole from 'jest-mock-console'; diff --git a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts index 2faef1f2..59e05152 100644 --- a/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts +++ b/packages/core/tests/unit/runtime/subscription/container/SubscriptionContainer.test.ts @@ -1,5 +1,5 @@ import { Agile, Observer, SubscriptionContainer } from '../../../../../src'; -import * as Utils from '../../../../../src/utils'; +import * as Utils from '@agile-ts/utils'; import mockConsole from 'jest-mock-console'; describe('SubscriptionContainer Tests', () => { diff --git a/packages/core/tests/unit/state/state.test.ts b/packages/core/tests/unit/state/state.test.ts index 5d11acdc..759628a4 100644 --- a/packages/core/tests/unit/state/state.test.ts +++ b/packages/core/tests/unit/state/state.test.ts @@ -6,7 +6,7 @@ import { StatePersistent, ComputedTracker, } from '../../../src'; -import * as Utils from '../../../src/utils'; +import * as Utils from '@agile-ts/utils'; import mockConsole from 'jest-mock-console'; jest.mock('../../../src/state/state.persistent'); diff --git a/packages/core/tests/unit/utils.test.ts b/packages/core/tests/unit/utils.test.ts index 344acea0..0b43d886 100644 --- a/packages/core/tests/unit/utils.test.ts +++ b/packages/core/tests/unit/utils.test.ts @@ -1,17 +1,4 @@ import { - clone, - copy, - defineConfig, - equal, - flatMerge, - generateId, - includesArray, - isAsyncFunction, - isFunction, - isJsonString, - isValidObject, - normalizeArray, - notEqual, globalBind, getAgileInstance, extractObservers, @@ -19,9 +6,7 @@ import { State, Observer, Collection, - createArrayFromObject, StateObserver, - removeProperties, } from '../../src'; import mockConsole from 'jest-mock-console'; @@ -38,172 +23,6 @@ describe('Utils Tests', () => { globalThis = {}; }); - describe('copy function tests', () => { - it('should copy Array without any reference', () => { - const myArray = [1, 2, 3, 4, 5]; - const myCopiedArray = copy(myArray); - const myDateArray = [new Date(), 2, new Date(), new Date()]; - const myCopiedDateArray = copy(myDateArray); - - expect(myCopiedArray).toStrictEqual([1, 2, 3, 4, 5]); - expect(myArray).toStrictEqual([1, 2, 3, 4, 5]); - expect(myCopiedDateArray).toStrictEqual(myDateArray); - expect(myDateArray).toStrictEqual(myDateArray); - - myCopiedArray.push(6); - myCopiedDateArray.push(1); - - expect(myCopiedArray).toStrictEqual([1, 2, 3, 4, 5, 6]); - expect(myArray).toStrictEqual([1, 2, 3, 4, 5]); - expect(myCopiedDateArray).not.toStrictEqual(myDateArray); - expect(myDateArray).toStrictEqual(myDateArray); - }); - - it('should copy Object without any reference', () => { - const myObject = { id: 1, name: 'jeff' }; - const myCopiedObject = copy(myObject); - - expect(myCopiedObject).toStrictEqual({ id: 1, name: 'jeff' }); - expect(myObject).toStrictEqual({ id: 1, name: 'jeff' }); - - myObject.name = 'hans'; - - expect(myObject).toStrictEqual({ id: 1, name: 'hans' }); - expect(myCopiedObject).toStrictEqual({ id: 1, name: 'jeff' }); - }); - - it('should copy deep Object without any reference', () => { - const myObject = { - id: 1, - name: 'jeff', - location: { country: 'Germany', state: 'Bayern' }, - }; - const myCopiedObject = copy(myObject); - - expect(myCopiedObject).toStrictEqual({ - id: 1, - name: 'jeff', - location: { country: 'Germany', state: 'Bayern' }, - }); - expect(myObject).toStrictEqual({ - id: 1, - name: 'jeff', - location: { country: 'Germany', state: 'Bayern' }, - }); - - myObject.name = 'hans'; - myObject.location.state = 'Sachsen'; - - expect(myObject).toStrictEqual({ - id: 1, - name: 'hans', - location: { country: 'Germany', state: 'Sachsen' }, - }); - expect(myCopiedObject).toStrictEqual({ - id: 1, - name: 'jeff', - location: { country: 'Germany', state: 'Bayern' }, - }); - }); - - it('should copy default Types', () => { - const myNumber = 5; - const myCopiedNumber = copy(myNumber); - const myString = 'frank'; - const myCopiedString = copy(myString); - const myNull = null; - const myCopiedNull = copy(myNull); - const myUndefined = undefined; - const myCopiedUndefined = copy(myUndefined); - - expect(myCopiedNumber).toBe(5); - expect(myNumber).toBe(5); - expect(myCopiedString).toBe('frank'); - expect(myString).toBe('frank'); - expect(myCopiedNull).toBe(null); - expect(myNull).toBe(null); - expect(myCopiedUndefined).toBe(undefined); - expect(myUndefined).toBe(undefined); - }); - - it("shouldn't copy classes", () => { - const myDate = new Date(); - const myCopiedDate = copy(myDate); - - expect(myCopiedDate).toBe(myDate); - expect(myDate).toBe(myDate); - }); - }); - - describe('isValidObject function tests', () => { - // Can't be Tested in not Web-Environment - // it("should return false if passing HTML Element", () => { - // expect(isValidObject(HTMLElement)).toBeFalsy(); - // }); - - it('should return false if passed instance is invalid Object (default config)', () => { - expect(isValidObject(null)).toBeFalsy(); - expect(isValidObject('Hello')).toBeFalsy(); - expect(isValidObject([1, 2])).toBeFalsy(); - expect(isValidObject(123)).toBeFalsy(); - }); - - it('should return true if passed instance is valid Object (default config)', () => { - expect(isValidObject({ hello: 'jeff' })).toBeTruthy(); - expect( - isValidObject({ hello: 'jeff', deep: { hello: 'franz' } }) - ).toBeTruthy(); - }); - - it('should return true if passed instance is array (considerArray = true)', () => { - expect(isValidObject([1, 2], true)).toBeTruthy(); - }); - }); - - describe('includesArray function tests', () => { - it("should return false if Array1 doesn't include Array2", () => { - expect(includesArray([1, 2], [5, 6])).toBeFalsy(); - }); - - it('should return false if Array1 does only include parts of Array2', () => { - expect(includesArray([1, 2], [2, 6])).toBeFalsy(); - }); - - it('should return true if Array1 includes Array2', () => { - expect(includesArray([1, 4, 2, 3], [1, 2])).toBeTruthy(); - }); - - it('should return true if Array1 is equal to Array2', () => { - expect(includesArray([1, 2], [1, 2])).toBeTruthy(); - }); - }); - - describe('normalizeArray function tests', () => { - it('should normalize Array (default config)', () => { - expect(normalizeArray([1, 2, undefined, 3, 'hi'])).toStrictEqual([ - 1, - 2, - undefined, - 3, - 'hi', - ]); - }); - - it('should normalize single Item (default config)', () => { - expect(normalizeArray(1)).toStrictEqual([1]); - }); - - it("shouldn't normalize undefined (default config)", () => { - expect(normalizeArray(undefined)).toStrictEqual([]); - }); - - it('should normalize undefined (config.createUndefinedArray = true)', () => { - expect( - normalizeArray(undefined, { createUndefinedArray: true }) - ).toStrictEqual([undefined]); - }); - }); - describe('getAgileInstance function tests', () => { beforeEach(() => { globalThis[Agile.globalKey] = dummyAgile; @@ -291,370 +110,6 @@ describe('Utils Tests', () => { }); }); - describe('isFunction function tests', () => { - it('should return true if passed instance is valid Function', () => { - expect( - isFunction(() => { - /* empty function */ - }) - ).toBeTruthy(); - }); - - it('should return false if passed instance is invalid Function', () => { - expect(isFunction('hello')).toBeFalsy(); - expect(isFunction(1)).toBeFalsy(); - expect(isFunction([1, 2, 3])).toBeFalsy(); - expect(isFunction({ hello: 'jeff' })).toBeFalsy(); - }); - }); - - describe('isAsyncFunction function tests', () => { - it('should return true if passed instance is valid async Function', () => { - expect( - isAsyncFunction(async () => { - /* empty function */ - }) - ).toBeTruthy(); - expect( - isAsyncFunction(async function () { - /* empty function */ - }) - ).toBeTruthy(); - }); - - it('should return false if passed instance is invalid async Function', () => { - expect(isAsyncFunction('hello')).toBeFalsy(); - expect(isAsyncFunction(1)).toBeFalsy(); - expect(isAsyncFunction([1, 2, 3])).toBeFalsy(); - expect(isAsyncFunction({ hello: 'jeff' })).toBeFalsy(); - expect( - isAsyncFunction(() => { - /* empty function */ - }) - ).toBeFalsy(); - expect( - isAsyncFunction(function () { - /* empty function */ - }) - ).toBeFalsy(); - }); - }); - - describe('isJsonString function tests', () => { - it('should return true if passed instance is valid Json String', () => { - expect(isJsonString('{"name":"John", "age":31, "city":"New York"}')).toBe( - true - ); - }); - - it('should return false if passed instance is invalid Json String', () => { - expect(isJsonString('frank')).toBeFalsy(); - expect(isJsonString('{name":"John", "age":31, "city":"New York"}')).toBe( - false - ); - expect(isJsonString(10)).toBeFalsy(); - expect(isJsonString({ name: 'John', age: 31 })).toBeFalsy(); - }); - }); - - describe('defineConfig function tests', () => { - it('should merge defaults into config and overwrite undefined properties (default config)', () => { - const config = { - allowLogging: true, - loops: 10, - isHuman: undefined, - }; - expect( - defineConfig(config, { - allowLogging: false, - loops: 15, - isHuman: true, - isRobot: false, - name: 'jeff', - }) - ).toStrictEqual({ - allowLogging: true, - loops: 10, - isHuman: true, - isRobot: false, - name: 'jeff', - }); - }); - - it("should merge defaults into config and shouldn't overwrite undefined properties (overwriteUndefinedProperties = false)", () => { - const config = { - allowLogging: true, - loops: 10, - isHuman: undefined, - }; - expect( - defineConfig( - config, - { - allowLogging: false, - loops: 15, - isHuman: true, - isRobot: false, - name: 'jeff', - }, - false - ) - ).toStrictEqual({ - allowLogging: true, - loops: 10, - isHuman: undefined, - isRobot: false, - name: 'jeff', - }); - }); - }); - - describe('flatMerge function tests', () => { - it('should merge Changes Object into Source Object', () => { - const source = { - id: 123, - name: 'jeff', - size: 189, - }; - expect( - flatMerge(source, { - name: 'hans', - size: 177, - }) - ).toStrictEqual({ - id: 123, - name: 'hans', - size: 177, - }); - }); - - it('should add new properties to Source Object', () => { - const source = { - id: 123, - name: 'jeff', - size: 189, - }; - - expect( - flatMerge(source, { - name: 'hans', - size: 177, - location: 'behind you', - }) - ).toStrictEqual({ - id: 123, - name: 'hans', - size: 177, - location: 'behind you', - }); - }); - - it("shouldn't add new properties to source Object (config.addNewProperties = false)", () => { - const source = { - id: 123, - name: 'jeff', - size: 189, - }; - - expect( - flatMerge( - source, - { - name: 'hans', - size: 177, - location: 'behind you', - }, - { addNewProperties: false } - ) - ).toStrictEqual({ - id: 123, - name: 'hans', - size: 177, - }); - }); - - it("shouldn't deep merge Changes Object into Source Object", () => { - const source = { - id: 123, - name: 'jeff', - address: { - place: 'JeffsHome', - country: 'Germany', - }, - }; - - expect( - flatMerge(source, { - place: 'JeffsNewHome', - }) - ).toStrictEqual({ - id: 123, - name: 'jeff', - address: { - place: 'JeffsHome', - country: 'Germany', - }, - place: 'JeffsNewHome', - }); - }); - }); - - describe('equal function tests', () => { - it('should return true if value1 and value2 are equal', () => { - expect(equal({ id: 123, name: 'jeff' }, { id: 123, name: 'jeff' })).toBe( - true - ); - expect(equal([1, 2, 3], [1, 2, 3])).toBeTruthy(); - expect(equal(12, 12)).toBeTruthy(); - expect(equal('hi', 'hi')).toBeTruthy(); - }); - - it("should return false if value1 and value2 aren't equal", () => { - expect(equal({ id: 123, name: 'jeff' }, { id: 123, name: 'hans' })).toBe( - false - ); - expect(equal([1, 2], [3, 5])).toBeFalsy(); - expect(equal(12, 13)).toBeFalsy(); - expect(equal('hi', 'bye')).toBeFalsy(); - }); - }); - - describe('notEqual function tests', () => { - it('should return false if value1 and value2 are equal', () => { - expect( - notEqual({ id: 123, name: 'jeff' }, { id: 123, name: 'jeff' }) - ).toBeFalsy(); - expect(notEqual([1, 2, 3], [1, 2, 3])).toBeFalsy(); - expect(notEqual(12, 12)).toBeFalsy(); - expect(equal('hi', 'bye')).toBeFalsy(); - }); - - it("should return true if value1 and value2 aren't equal", () => { - expect( - notEqual({ id: 123, name: 'jeff' }, { id: 123, name: 'hans' }) - ).toBeTruthy(); - expect(notEqual([1, 2], [3, 5])).toBeTruthy(); - expect(notEqual(12, 13)).toBeTruthy(); - expect(notEqual('hi', 'bye')).toBeTruthy(); - }); - }); - - describe('generateId function tests', () => { - it('should returned generated Id that matches regex', () => { - expect(generateId()).toMatch(/^[a-zA-Z0-9]*$/); - }); - - it('should returned generated Id with correct length (length = x)', () => { - expect(generateId(10)).toMatch(/^[a-zA-Z0-9]*$/); - expect(generateId(10).length).toEqual(10); - expect(generateId(5).length).toEqual(5); - expect(generateId(-10).length).toEqual(0); - }); - }); - - describe('createArrayFromObject function tests', () => { - it('should transform Object to Array', () => { - const dummyObject = { - jeff: { - hello: 'there', - }, - frank: { - see: 'you', - }, - hans: { - how: 'are you', - }, - }; - - const generatedArray = createArrayFromObject(dummyObject); - - expect(generatedArray).toStrictEqual([ - { - key: 'jeff', - instance: { - hello: 'there', - }, - }, - { - key: 'frank', - instance: { - see: 'you', - }, - }, - { - key: 'hans', - instance: { - how: 'are you', - }, - }, - ]); - }); - }); - - describe('clone function tests', () => { - it('should clone Object/Class without any reference', () => { - class DummyClass { - constructor( - public id: number, - public name: string, - public location: { country: string; state: string } - ) {} - } - const dummyClass = new DummyClass(10, 'jeff', { - country: 'USA', - state: 'California', - }); - const clonedDummyClass = clone(dummyClass); - - expect(dummyClass).toBeInstanceOf(DummyClass); - expect(clonedDummyClass).toBeInstanceOf(DummyClass); - expect(dummyClass.name).toBe('jeff'); - expect(dummyClass.id).toBe(10); - expect(dummyClass.location).toStrictEqual({ - country: 'USA', - state: 'California', - }); - expect(clonedDummyClass.name).toBe('jeff'); - expect(clonedDummyClass.id).toBe(10); - expect(clonedDummyClass.location).toStrictEqual({ - country: 'USA', - state: 'California', - }); - - dummyClass.name = 'frank'; - dummyClass.location.state = 'Florida'; - - expect(dummyClass.name).toBe('frank'); - expect(dummyClass.id).toBe(10); - expect(dummyClass.location).toStrictEqual({ - country: 'USA', - state: 'Florida', - }); - expect(clonedDummyClass.name).toBe('jeff'); - expect(clonedDummyClass.id).toBe(10); - expect(clonedDummyClass.location).toStrictEqual({ - country: 'USA', - state: 'California', - }); - }); - }); - - describe('removeProperties function tests', () => { - it('should remove properties from object and remove reference', () => { - const myObject = { id: 20, name: 'jeff', age: 10, location: 'Germany' }; - const newObject = removeProperties(myObject, ['location', 'age']); - newObject['size'] = 100.2; - - expect(myObject).toStrictEqual({ - id: 20, - name: 'jeff', - age: 10, - location: 'Germany', - }); - expect(newObject).toStrictEqual({ id: 20, name: 'jeff', size: 100.2 }); - }); - }); - describe('globalBind function tests', () => { const dummyKey = 'myDummyKey'; diff --git a/packages/event/tests/unit/event.test.ts b/packages/event/tests/unit/event.test.ts index 219f1a27..f2bdd61f 100644 --- a/packages/event/tests/unit/event.test.ts +++ b/packages/event/tests/unit/event.test.ts @@ -1,6 +1,6 @@ import { Event, EventObserver } from '../../src'; import { Agile, Observer } from '@agile-ts/core'; -import * as Utils from '@agile-ts/core/dist/utils'; +import * as Utils from '@agile-ts/utils'; import mockConsole from 'jest-mock-console'; describe('Event Tests', () => { diff --git a/packages/logger/LICENSE b/packages/logger/LICENSE new file mode 100644 index 00000000..b93156fb --- /dev/null +++ b/packages/logger/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-present bennodev19 + +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. diff --git a/packages/logger/README.md b/packages/logger/README.md new file mode 100644 index 00000000..f1812562 --- /dev/null +++ b/packages/logger/README.md @@ -0,0 +1 @@ +# [INTERNAL] Logger diff --git a/packages/logger/jest.config.js b/packages/logger/jest.config.js new file mode 100644 index 00000000..692d3d79 --- /dev/null +++ b/packages/logger/jest.config.js @@ -0,0 +1,11 @@ +const baseConfig = require('../../jest.base.config.js'); +const packageJson = require('./package.json'); +const packageName = packageJson.name.split('@agile-ts/').pop(); + +module.exports = { + ...baseConfig, + rootDir: '../..', + roots: [`/packages/${packageName}`], + name: packageName, + displayName: packageName, +}; diff --git a/packages/logger/package.json b/packages/logger/package.json new file mode 100644 index 00000000..5bd85eb3 --- /dev/null +++ b/packages/logger/package.json @@ -0,0 +1,51 @@ +{ + "name": "@agile-ts/logger", + "version": "0.0.1", + "author": "BennoDev", + "license": "MIT", + "homepage": "https://agile-ts.org/", + "description": "Simple Logger", + "keywords": [ + "logger", + "console.log", + "agile", + "agile-ts" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "prepare": "tsc && tsc -p ./tsconfig.production.json", + "dev:publish": "yalc publish", + "dev:push": "yalc push", + "watch:push": "tsc-watch --onSuccess \"yarn run dev:push\"", + "watch": "tsc -w", + "release": "yarn run prepare", + "preview": "npm pack", + "test": "jest", + "test:coverage": "jest --coverage", + "lint": "eslint src/**/*" + }, + "devDependencies": { + "@agile-ts/utils": "file:../utils" + }, + "dependencies": { + "@agile-ts/utils": "^0.0.1" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/agile-ts/agile.git" + }, + "bugs": { + "url": "https://github.com/agile-ts/agile/issues" + }, + "files": [ + "dist", + "LICENSE", + "README.md", + "CHANGELOG.md" + ] +} diff --git a/packages/core/src/logger/index.ts b/packages/logger/src/index.ts similarity index 99% rename from packages/core/src/logger/index.ts rename to packages/logger/src/index.ts index 213c2455..d00a98ea 100644 --- a/packages/core/src/logger/index.ts +++ b/packages/logger/src/index.ts @@ -1,10 +1,10 @@ import { defineConfig, - includesArray, - isValidObject, generateId, + includesArray, isFunction, -} from '../internal'; + isValidObject, +} from '@agile-ts/utils'; export class Logger { public key?: LoggerKey; diff --git a/packages/logger/tsconfig.json b/packages/logger/tsconfig.json new file mode 100644 index 00000000..600cd205 --- /dev/null +++ b/packages/logger/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.default.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": [ + "./src/**/*" // Only include what is in src (-> dist, tests, .. will be excluded) + ] +} \ No newline at end of file diff --git a/packages/logger/tsconfig.production.json b/packages/logger/tsconfig.production.json new file mode 100644 index 00000000..4b5c4d12 --- /dev/null +++ b/packages/logger/tsconfig.production.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": false, + "removeComments": true + } +} \ No newline at end of file diff --git a/packages/react/package.json b/packages/react/package.json index 406ab038..d7d93f83 100644 --- a/packages/react/package.json +++ b/packages/react/package.json @@ -45,8 +45,10 @@ }, "peerDependencies": { "react": "^16.13.1", - "@agile-ts/core": "^0.0.14", - "@agile-ts/proxytree": "0.0.1" + "@agile-ts/core": "^0.0.14" + }, + "dependencies": { + "@agile-ts/proxytree": "^0.0.1" }, "publishConfig": { "access": "public" diff --git a/packages/utils/LICENSE b/packages/utils/LICENSE new file mode 100644 index 00000000..b93156fb --- /dev/null +++ b/packages/utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2020-present bennodev19 + +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. diff --git a/packages/utils/README.md b/packages/utils/README.md new file mode 100644 index 00000000..e89c4210 --- /dev/null +++ b/packages/utils/README.md @@ -0,0 +1 @@ +# [INTERNAL] Utils diff --git a/packages/utils/jest.config.js b/packages/utils/jest.config.js new file mode 100644 index 00000000..692d3d79 --- /dev/null +++ b/packages/utils/jest.config.js @@ -0,0 +1,11 @@ +const baseConfig = require('../../jest.base.config.js'); +const packageJson = require('./package.json'); +const packageName = packageJson.name.split('@agile-ts/').pop(); + +module.exports = { + ...baseConfig, + rootDir: '../..', + roots: [`/packages/${packageName}`], + name: packageName, + displayName: packageName, +}; diff --git a/packages/utils/package.json b/packages/utils/package.json new file mode 100644 index 00000000..e64956b3 --- /dev/null +++ b/packages/utils/package.json @@ -0,0 +1,44 @@ +{ + "name": "@agile-ts/utils", + "version": "0.0.1", + "author": "BennoDev", + "license": "MIT", + "homepage": "https://agile-ts.org/", + "description": "Util methods of AgileTs", + "keywords": [ + "utils", + "agile", + "agile-ts" + ], + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "build": "tsc", + "prepare": "tsc && tsc -p ./tsconfig.production.json", + "dev:publish": "yalc publish", + "dev:push": "yalc push", + "watch:push": "tsc-watch --onSuccess \"yarn run dev:push\"", + "watch": "tsc -w", + "release": "yarn run prepare", + "preview": "npm pack", + "test": "jest", + "test:coverage": "jest --coverage", + "lint": "eslint src/**/*" + }, + "publishConfig": { + "access": "public" + }, + "repository": { + "type": "git", + "url": "https://github.com/agile-ts/agile.git" + }, + "bugs": { + "url": "https://github.com/agile-ts/agile/issues" + }, + "files": [ + "dist", + "LICENSE", + "README.md", + "CHANGELOG.md" + ] +} diff --git a/packages/utils/src/index.ts b/packages/utils/src/index.ts new file mode 100644 index 00000000..175c8eaf --- /dev/null +++ b/packages/utils/src/index.ts @@ -0,0 +1,315 @@ +//========================================================================================================= +// Copy +//========================================================================================================= +/** + * @internal + * Creates a fresh copy of an Array/Object + * https://www.samanthaming.com/tidbits/70-3-ways-to-clone-objects/ + * @param value - Array/Object that gets copied + */ +export function copy(value: T): T { + // Extra checking '!value' because 'typeof null === object' + if (!value) return value; + + // Ignore everything that is no object or array + const valConstructorName = Object.getPrototypeOf(value).constructor.name; + if (!['object', 'array'].includes(valConstructorName.toLowerCase())) + return value; + + let temp; + const newObject: any = Array.isArray(value) ? [] : {}; + for (const property in value) { + temp = value[property]; + newObject[property] = typeof temp === 'object' ? copy(temp) : temp; + } + return newObject as T; +} + +//========================================================================================================= +// Is Valid Object +//========================================================================================================= +/** + * @internal + * Checks if passed value is a valid Object + * https://stackoverflow.com/questions/12996871/why-does-typeof-array-with-objects-return-object-and-not-array + * @param value - Value that is tested for its correctness + * @param considerArray - Whether Arrays should be considered as object + */ +export function isValidObject(value: any, considerArray = false): boolean { + function isHTMLElement(obj: any) { + try { + return obj instanceof HTMLElement; + } catch (e) { + return ( + typeof obj === 'object' && + obj.nodeType === 1 && + typeof obj.style === 'object' && + typeof obj.ownerDocument === 'object' + ); + } + } + + return ( + value !== null && + typeof value === 'object' && + !isHTMLElement(value) && + (considerArray ? true : !Array.isArray(value)) + ); +} + +//========================================================================================================= +// Includes Array +//========================================================================================================= +/** + * @internal + * Check if array1 contains all elements of array2 + * @param array1 - Array 1 + * @param array2 - Array 2 + */ +export function includesArray( + array1: Array, + array2: Array +): boolean { + return array2.every((element) => array1.includes(element)); +} + +//========================================================================================================= +// Normalize Array +//========================================================================================================= +/** + * @internal + * Transforms Item/s to an Item Array + * @param items - Item/s that gets transformed to an Array + * @param config - Config + */ +export function normalizeArray( + items?: DataType | Array, + config: { createUndefinedArray?: boolean } = {} +): Array { + config = defineConfig(config, { + createUndefinedArray: false, // If it should return [] or [undefined] if the passed Item is undefined + }); + if (!items && !config.createUndefinedArray) return []; + return Array.isArray(items) ? items : [items as DataType]; +} + +//========================================================================================================= +// Is Function +//========================================================================================================= +/** + * @internal + * Checks if value is a function + * @param value - Value that gets tested if its a function + */ +export function isFunction(value: any): boolean { + return typeof value === 'function'; +} + +//========================================================================================================= +// Is Async Function +//========================================================================================================= +/** + * @internal + * Checks if value is an async function + * @param value - Value that gets tested if its an async function + */ +export function isAsyncFunction(value: any): boolean { + const valueString = value.toString(); + return ( + isFunction(value) && + (value.constructor.name === 'AsyncFunction' || + valueString.includes('__awaiter')) + ); +} + +//========================================================================================================= +// Is Json String +//========================================================================================================= +/** + * @internal + * Checks if value is valid JsonString + * @param value - Value that gets checked + */ +export function isJsonString(value: any): boolean { + if (typeof value !== 'string') return false; + try { + JSON.parse(value); + } catch (e) { + return false; + } + return true; +} + +//========================================================================================================= +// Define Config +//========================================================================================================= +/** + * @internal + * Merges default values/properties into config object + * @param config - Config object that receives default values + * @param defaults - Default values object that gets merged into config object + * @param overwriteUndefinedProperties - If undefined Properties in config gets overwritten by the default value + */ +export function defineConfig( + config: ConfigInterface, + defaults: Object, + overwriteUndefinedProperties?: boolean +): ConfigInterface { + if (overwriteUndefinedProperties === undefined) + overwriteUndefinedProperties = true; + + if (overwriteUndefinedProperties) { + const finalConfig = { ...defaults, ...config }; + for (const key in finalConfig) + if (finalConfig[key] === undefined) finalConfig[key] = defaults[key]; + return finalConfig; + } + + return { ...defaults, ...config }; +} + +//========================================================================================================= +// Flat Merge +//========================================================================================================= +/** + * @internal + * @param addNewProperties - Adds new properties to source Object + */ +export interface FlatMergeConfigInterface { + addNewProperties?: boolean; +} + +/** + * @internal + * Merges items into object, be aware that the merge will only happen at the top level of the object. + * Initially it adds new properties of the changes object into the source object. + * @param source - Source object + * @param changes - Changes that get merged into the source object + * @param config - Config + */ +export function flatMerge( + source: DataType, + changes: Object, + config: FlatMergeConfigInterface = {} +): DataType { + config = defineConfig(config, { + addNewProperties: true, + }); + + // Copy Source to avoid References + const _source = copy(source); + if (!_source) return _source; + + // Merge Changes Object into Source Object + const keys = Object.keys(changes); + keys.forEach((property) => { + if (!config.addNewProperties && !_source[property]) return; + _source[property] = changes[property]; + }); + + return _source; +} + +//========================================================================================================= +// Equals +//========================================================================================================= +/** + * @internal + * Check if two values are equal + * @param value1 - First Value + * @param value2 - Second Value + */ +export function equal(value1: any, value2: any): boolean { + return value1 === value2 || JSON.stringify(value1) === JSON.stringify(value2); +} + +//========================================================================================================= +// Not Equals +//========================================================================================================= +/** + * @internal + * Checks if two values aren't equal + * @param value1 - First Value + * @param value2 - Second Value + */ +export function notEqual(value1: any, value2: any): boolean { + return !equal(value1, value2); +} + +//========================================================================================================= +// Generate Id +//========================================================================================================= +/** + * @internal + * Generates random Id + * @param length - Length of generated Id + */ +export function generateId(length?: number): string { + const characters = + 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'; + const charactersLength = characters.length; + let result = ''; + if (!length) length = 5; + for (let i = 0; i < length; i++) { + result += characters.charAt(Math.floor(Math.random() * charactersLength)); + } + return result; +} + +//========================================================================================================= +// Create Array From Object +//========================================================================================================= +/** + * @internal + * Transforms Object to Array + * @param object - Object that gets transformed + */ +export function createArrayFromObject

(object: { + [key: string]: P; +}): Array<{ key: string; instance: P }> { + const array: Array<{ key: string; instance: P }> = []; + for (const key in object) { + array.push({ + key: key, + instance: object[key], + }); + } + return array; +} + +//========================================================================================================= +// Clone +//========================================================================================================= +/** + * @internal + * Clones a Class + * @param instance - Instance of Class you want to clone + */ +export function clone(instance: T): T { + // Clone Class + const objectCopy: T = Object.create(Object.getPrototypeOf(instance)); + const objectClone = Object.assign(objectCopy, instance); + + // Copy Properties of Class to remove flat references + for (const key in objectClone) objectClone[key] = copy(objectClone[key]); + + return objectClone; +} + +//========================================================================================================= +// Remove Properties +//========================================================================================================= +/** + * @internal + * Removes properties from Object + * @param object - Object from which the properties get removed + * @param properties - Properties that get removed from the object + */ +export function removeProperties( + object: T, + properties: Array +): T { + const copiedObject = copy(object); + properties.map((property) => delete copiedObject[property]); + return copiedObject; +} diff --git a/packages/utils/tests/unit/utils.test.ts b/packages/utils/tests/unit/utils.test.ts new file mode 100644 index 00000000..6156551c --- /dev/null +++ b/packages/utils/tests/unit/utils.test.ts @@ -0,0 +1,555 @@ +import { + clone, + copy, + defineConfig, + equal, + flatMerge, + generateId, + includesArray, + isAsyncFunction, + isFunction, + isJsonString, + isValidObject, + normalizeArray, + notEqual, + createArrayFromObject, + removeProperties, +} from '../../src'; +import mockConsole from 'jest-mock-console'; + +describe('Utils Tests', () => { + beforeEach(() => { + jest.clearAllMocks(); + mockConsole(['error', 'warn']); + }); + + describe('copy function tests', () => { + it('should copy Array without any reference', () => { + const myArray = [1, 2, 3, 4, 5]; + const myCopiedArray = copy(myArray); + const myDateArray = [new Date(), 2, new Date(), new Date()]; + const myCopiedDateArray = copy(myDateArray); + + expect(myCopiedArray).toStrictEqual([1, 2, 3, 4, 5]); + expect(myArray).toStrictEqual([1, 2, 3, 4, 5]); + expect(myCopiedDateArray).toStrictEqual(myDateArray); + expect(myDateArray).toStrictEqual(myDateArray); + + myCopiedArray.push(6); + myCopiedDateArray.push(1); + + expect(myCopiedArray).toStrictEqual([1, 2, 3, 4, 5, 6]); + expect(myArray).toStrictEqual([1, 2, 3, 4, 5]); + expect(myCopiedDateArray).not.toStrictEqual(myDateArray); + expect(myDateArray).toStrictEqual(myDateArray); + }); + + it('should copy Object without any reference', () => { + const myObject = { id: 1, name: 'jeff' }; + const myCopiedObject = copy(myObject); + + expect(myCopiedObject).toStrictEqual({ id: 1, name: 'jeff' }); + expect(myObject).toStrictEqual({ id: 1, name: 'jeff' }); + + myObject.name = 'hans'; + + expect(myObject).toStrictEqual({ id: 1, name: 'hans' }); + expect(myCopiedObject).toStrictEqual({ id: 1, name: 'jeff' }); + }); + + it('should copy deep Object without any reference', () => { + const myObject = { + id: 1, + name: 'jeff', + location: { country: 'Germany', state: 'Bayern' }, + }; + const myCopiedObject = copy(myObject); + + expect(myCopiedObject).toStrictEqual({ + id: 1, + name: 'jeff', + location: { country: 'Germany', state: 'Bayern' }, + }); + expect(myObject).toStrictEqual({ + id: 1, + name: 'jeff', + location: { country: 'Germany', state: 'Bayern' }, + }); + + myObject.name = 'hans'; + myObject.location.state = 'Sachsen'; + + expect(myObject).toStrictEqual({ + id: 1, + name: 'hans', + location: { country: 'Germany', state: 'Sachsen' }, + }); + expect(myCopiedObject).toStrictEqual({ + id: 1, + name: 'jeff', + location: { country: 'Germany', state: 'Bayern' }, + }); + }); + + it('should copy default Types', () => { + const myNumber = 5; + const myCopiedNumber = copy(myNumber); + const myString = 'frank'; + const myCopiedString = copy(myString); + const myNull = null; + const myCopiedNull = copy(myNull); + const myUndefined = undefined; + const myCopiedUndefined = copy(myUndefined); + + expect(myCopiedNumber).toBe(5); + expect(myNumber).toBe(5); + expect(myCopiedString).toBe('frank'); + expect(myString).toBe('frank'); + expect(myCopiedNull).toBe(null); + expect(myNull).toBe(null); + expect(myCopiedUndefined).toBe(undefined); + expect(myUndefined).toBe(undefined); + }); + + it("shouldn't copy classes", () => { + const myDate = new Date(); + const myCopiedDate = copy(myDate); + + expect(myCopiedDate).toBe(myDate); + expect(myDate).toBe(myDate); + }); + }); + + describe('isValidObject function tests', () => { + // Can't be Tested in not Web-Environment + // it("should return false if passing HTML Element", () => { + // expect(isValidObject(HTMLElement)).toBeFalsy(); + // }); + + it('should return false if passed instance is invalid Object (default config)', () => { + expect(isValidObject(null)).toBeFalsy(); + expect(isValidObject('Hello')).toBeFalsy(); + expect(isValidObject([1, 2])).toBeFalsy(); + expect(isValidObject(123)).toBeFalsy(); + }); + + it('should return true if passed instance is valid Object (default config)', () => { + expect(isValidObject({ hello: 'jeff' })).toBeTruthy(); + expect( + isValidObject({ hello: 'jeff', deep: { hello: 'franz' } }) + ).toBeTruthy(); + }); + + it('should return true if passed instance is array (considerArray = true)', () => { + expect(isValidObject([1, 2], true)).toBeTruthy(); + }); + }); + + describe('includesArray function tests', () => { + it("should return false if Array1 doesn't include Array2", () => { + expect(includesArray([1, 2], [5, 6])).toBeFalsy(); + }); + + it('should return false if Array1 does only include parts of Array2', () => { + expect(includesArray([1, 2], [2, 6])).toBeFalsy(); + }); + + it('should return true if Array1 includes Array2', () => { + expect(includesArray([1, 4, 2, 3], [1, 2])).toBeTruthy(); + }); + + it('should return true if Array1 is equal to Array2', () => { + expect(includesArray([1, 2], [1, 2])).toBeTruthy(); + }); + }); + + describe('normalizeArray function tests', () => { + it('should normalize Array (default config)', () => { + expect(normalizeArray([1, 2, undefined, 3, 'hi'])).toStrictEqual([ + 1, + 2, + undefined, + 3, + 'hi', + ]); + }); + + it('should normalize single Item (default config)', () => { + expect(normalizeArray(1)).toStrictEqual([1]); + }); + + it("shouldn't normalize undefined (default config)", () => { + expect(normalizeArray(undefined)).toStrictEqual([]); + }); + + it('should normalize undefined (config.createUndefinedArray = true)', () => { + expect( + normalizeArray(undefined, { createUndefinedArray: true }) + ).toStrictEqual([undefined]); + }); + }); + + describe('isFunction function tests', () => { + it('should return true if passed instance is valid Function', () => { + expect( + isFunction(() => { + /* empty function */ + }) + ).toBeTruthy(); + }); + + it('should return false if passed instance is invalid Function', () => { + expect(isFunction('hello')).toBeFalsy(); + expect(isFunction(1)).toBeFalsy(); + expect(isFunction([1, 2, 3])).toBeFalsy(); + expect(isFunction({ hello: 'jeff' })).toBeFalsy(); + }); + }); + + describe('isAsyncFunction function tests', () => { + it('should return true if passed instance is valid async Function', () => { + expect( + isAsyncFunction(async () => { + /* empty function */ + }) + ).toBeTruthy(); + expect( + isAsyncFunction(async function () { + /* empty function */ + }) + ).toBeTruthy(); + }); + + it('should return false if passed instance is invalid async Function', () => { + expect(isAsyncFunction('hello')).toBeFalsy(); + expect(isAsyncFunction(1)).toBeFalsy(); + expect(isAsyncFunction([1, 2, 3])).toBeFalsy(); + expect(isAsyncFunction({ hello: 'jeff' })).toBeFalsy(); + expect( + isAsyncFunction(() => { + /* empty function */ + }) + ).toBeFalsy(); + expect( + isAsyncFunction(function () { + /* empty function */ + }) + ).toBeFalsy(); + }); + }); + + describe('isJsonString function tests', () => { + it('should return true if passed instance is valid Json String', () => { + expect(isJsonString('{"name":"John", "age":31, "city":"New York"}')).toBe( + true + ); + }); + + it('should return false if passed instance is invalid Json String', () => { + expect(isJsonString('frank')).toBeFalsy(); + expect(isJsonString('{name":"John", "age":31, "city":"New York"}')).toBe( + false + ); + expect(isJsonString(10)).toBeFalsy(); + expect(isJsonString({ name: 'John', age: 31 })).toBeFalsy(); + }); + }); + + describe('defineConfig function tests', () => { + it('should merge defaults into config and overwrite undefined properties (default config)', () => { + const config = { + allowLogging: true, + loops: 10, + isHuman: undefined, + }; + expect( + defineConfig(config, { + allowLogging: false, + loops: 15, + isHuman: true, + isRobot: false, + name: 'jeff', + }) + ).toStrictEqual({ + allowLogging: true, + loops: 10, + isHuman: true, + isRobot: false, + name: 'jeff', + }); + }); + + it("should merge defaults into config and shouldn't overwrite undefined properties (overwriteUndefinedProperties = false)", () => { + const config = { + allowLogging: true, + loops: 10, + isHuman: undefined, + }; + expect( + defineConfig( + config, + { + allowLogging: false, + loops: 15, + isHuman: true, + isRobot: false, + name: 'jeff', + }, + false + ) + ).toStrictEqual({ + allowLogging: true, + loops: 10, + isHuman: undefined, + isRobot: false, + name: 'jeff', + }); + }); + }); + + describe('flatMerge function tests', () => { + it('should merge Changes Object into Source Object', () => { + const source = { + id: 123, + name: 'jeff', + size: 189, + }; + expect( + flatMerge(source, { + name: 'hans', + size: 177, + }) + ).toStrictEqual({ + id: 123, + name: 'hans', + size: 177, + }); + }); + + it('should add new properties to Source Object', () => { + const source = { + id: 123, + name: 'jeff', + size: 189, + }; + + expect( + flatMerge(source, { + name: 'hans', + size: 177, + location: 'behind you', + }) + ).toStrictEqual({ + id: 123, + name: 'hans', + size: 177, + location: 'behind you', + }); + }); + + it("shouldn't add new properties to source Object (config.addNewProperties = false)", () => { + const source = { + id: 123, + name: 'jeff', + size: 189, + }; + + expect( + flatMerge( + source, + { + name: 'hans', + size: 177, + location: 'behind you', + }, + { addNewProperties: false } + ) + ).toStrictEqual({ + id: 123, + name: 'hans', + size: 177, + }); + }); + + it("shouldn't deep merge Changes Object into Source Object", () => { + const source = { + id: 123, + name: 'jeff', + address: { + place: 'JeffsHome', + country: 'Germany', + }, + }; + + expect( + flatMerge(source, { + place: 'JeffsNewHome', + }) + ).toStrictEqual({ + id: 123, + name: 'jeff', + address: { + place: 'JeffsHome', + country: 'Germany', + }, + place: 'JeffsNewHome', + }); + }); + }); + + describe('equal function tests', () => { + it('should return true if value1 and value2 are equal', () => { + expect(equal({ id: 123, name: 'jeff' }, { id: 123, name: 'jeff' })).toBe( + true + ); + expect(equal([1, 2, 3], [1, 2, 3])).toBeTruthy(); + expect(equal(12, 12)).toBeTruthy(); + expect(equal('hi', 'hi')).toBeTruthy(); + }); + + it("should return false if value1 and value2 aren't equal", () => { + expect(equal({ id: 123, name: 'jeff' }, { id: 123, name: 'hans' })).toBe( + false + ); + expect(equal([1, 2], [3, 5])).toBeFalsy(); + expect(equal(12, 13)).toBeFalsy(); + expect(equal('hi', 'bye')).toBeFalsy(); + }); + }); + + describe('notEqual function tests', () => { + it('should return false if value1 and value2 are equal', () => { + expect( + notEqual({ id: 123, name: 'jeff' }, { id: 123, name: 'jeff' }) + ).toBeFalsy(); + expect(notEqual([1, 2, 3], [1, 2, 3])).toBeFalsy(); + expect(notEqual(12, 12)).toBeFalsy(); + expect(equal('hi', 'bye')).toBeFalsy(); + }); + + it("should return true if value1 and value2 aren't equal", () => { + expect( + notEqual({ id: 123, name: 'jeff' }, { id: 123, name: 'hans' }) + ).toBeTruthy(); + expect(notEqual([1, 2], [3, 5])).toBeTruthy(); + expect(notEqual(12, 13)).toBeTruthy(); + expect(notEqual('hi', 'bye')).toBeTruthy(); + }); + }); + + describe('generateId function tests', () => { + it('should returned generated Id that matches regex', () => { + expect(generateId()).toMatch(/^[a-zA-Z0-9]*$/); + }); + + it('should returned generated Id with correct length (length = x)', () => { + expect(generateId(10)).toMatch(/^[a-zA-Z0-9]*$/); + expect(generateId(10).length).toEqual(10); + expect(generateId(5).length).toEqual(5); + expect(generateId(-10).length).toEqual(0); + }); + }); + + describe('createArrayFromObject function tests', () => { + it('should transform Object to Array', () => { + const dummyObject = { + jeff: { + hello: 'there', + }, + frank: { + see: 'you', + }, + hans: { + how: 'are you', + }, + }; + + const generatedArray = createArrayFromObject(dummyObject); + + expect(generatedArray).toStrictEqual([ + { + key: 'jeff', + instance: { + hello: 'there', + }, + }, + { + key: 'frank', + instance: { + see: 'you', + }, + }, + { + key: 'hans', + instance: { + how: 'are you', + }, + }, + ]); + }); + }); + + describe('clone function tests', () => { + it('should clone Object/Class without any reference', () => { + class DummyClass { + constructor( + public id: number, + public name: string, + public location: { country: string; state: string } + ) {} + } + const dummyClass = new DummyClass(10, 'jeff', { + country: 'USA', + state: 'California', + }); + const clonedDummyClass = clone(dummyClass); + + expect(dummyClass).toBeInstanceOf(DummyClass); + expect(clonedDummyClass).toBeInstanceOf(DummyClass); + expect(dummyClass.name).toBe('jeff'); + expect(dummyClass.id).toBe(10); + expect(dummyClass.location).toStrictEqual({ + country: 'USA', + state: 'California', + }); + expect(clonedDummyClass.name).toBe('jeff'); + expect(clonedDummyClass.id).toBe(10); + expect(clonedDummyClass.location).toStrictEqual({ + country: 'USA', + state: 'California', + }); + + dummyClass.name = 'frank'; + dummyClass.location.state = 'Florida'; + + expect(dummyClass.name).toBe('frank'); + expect(dummyClass.id).toBe(10); + expect(dummyClass.location).toStrictEqual({ + country: 'USA', + state: 'Florida', + }); + expect(clonedDummyClass.name).toBe('jeff'); + expect(clonedDummyClass.id).toBe(10); + expect(clonedDummyClass.location).toStrictEqual({ + country: 'USA', + state: 'California', + }); + }); + }); + + describe('removeProperties function tests', () => { + it('should remove properties from object and remove reference', () => { + const myObject = { id: 20, name: 'jeff', age: 10, location: 'Germany' }; + const newObject = removeProperties(myObject, ['location', 'age']); + newObject['size'] = 100.2; + + expect(myObject).toStrictEqual({ + id: 20, + name: 'jeff', + age: 10, + location: 'Germany', + }); + expect(newObject).toStrictEqual({ id: 20, name: 'jeff', size: 100.2 }); + }); + }); +}); diff --git a/packages/utils/tsconfig.json b/packages/utils/tsconfig.json new file mode 100644 index 00000000..600cd205 --- /dev/null +++ b/packages/utils/tsconfig.json @@ -0,0 +1,10 @@ +{ + "extends": "../tsconfig.default.json", + "compilerOptions": { + "rootDir": "src", + "outDir": "dist" + }, + "include": [ + "./src/**/*" // Only include what is in src (-> dist, tests, .. will be excluded) + ] +} \ No newline at end of file diff --git a/packages/utils/tsconfig.production.json b/packages/utils/tsconfig.production.json new file mode 100644 index 00000000..4b5c4d12 --- /dev/null +++ b/packages/utils/tsconfig.production.json @@ -0,0 +1,7 @@ +{ + "extends": "./tsconfig.json", + "compilerOptions": { + "declaration": false, + "removeComments": true + } +} \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index d9c02297..4bf3705b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4,9 +4,9 @@ "@agile-ts/core@file:packages/core": version "0.0.14" - -"@agile-ts/proxytree@file:packages/proxytree": - version "0.0.1" + dependencies: + "@agile-ts/logger" "^0.0.1" + "@agile-ts/utils" "^0.0.1" "@akryum/winattr@^3.0.0": version "3.0.0"