Skip to content

Instantly share code, notes, and snippets.

@kerbyfc
Last active February 21, 2018 10:20
Show Gist options
  • Save kerbyfc/06075c049176712c23b7f5eb54876d77 to your computer and use it in GitHub Desktop.
Save kerbyfc/06075c049176712c23b7f5eb54876d77 to your computer and use it in GitHub Desktop.
Password strength validator
describe('immutableArrayMerge', () => {
it('should merge simple arrays', () => {
immutableArrayMerge([1, 3], [2, , 5]).should.be.eql([2, 3, 5]);
immutableArrayMerge([1, 3], [2, undefined, 5]).should.be.eql([2, undefined, 5]);
});
it('should merge arrays with objects', () => {
immutableArrayMerge([{a: [{b: 2, c: [3]}]}], [{a: [{b: 3, c: [4, 5]}]}]).should.be.eql([{a: [{b: 3, c: [4, 5]}]}]);
});
});
describe('immutableMerge', () => {
it('should merge simple objects', () => {
immutableMerge({a: 1}, {b: 2}).should.be.eql({a: 1, b: 2});
});
it('should merge objects with array values', () => {
immutableMerge({a: [1]}, {a: [2]}).should.be.eql({a: [2]});
immutableMerge({a: [1, 2, 3, 4, 5, 6]}, {a: [, , , , 10]}).should.be.eql({a: [1, 2, 3, 4, 10, 6]});
});
it('should merge complex objects', () => {
immutableMerge({f: [{a: [{b: 2, c: [3]}]}]}, {f: [{a: [{b: 3, c: [4, 5]}]}]}).should.be.eql({f: [{a: [{b: 3, c: [4, 5]}]}]});
});
it('should use customizer while merging objects', () => {
immutableMerge({a: [1, 2, 3]}, {a: [4, 5, 6]}, {
mergeArrays: ({sourceValue, targetValue}) => {
return _.union(targetValue, sourceValue);
},
}).should.be.eql({a: [1, 2, 3, 4, 5, 6]});
});
});
import * as _ from 'lodash';
interface IMergeCustomizerOptions<TType> {
sourceValue: TType;
targetValue: TType;
key: string;
source: object | any[];
target: object | any[];
}
interface IMergeOptions {
mergeObjects?(options: IMergeCustomizerOptions<object>): any;
mergeArrays?(options: IMergeCustomizerOptions<any[]>): any;
mergePrimitives?(options: IMergeCustomizerOptions<any>): any;
}
/**
* @example immutableMerge(state, flat.unflatten({'path.to.state.array.properties.1': 1, 'path.to.state.prop': 2}));
*/
export function immutableMerge<TSrc extends {}, TMix extends {}>(source: TSrc, target: TMix, options: IMergeOptions = {}): TSrc & TMix {
return Object.keys(target).reduce((acc, key) => {
const targetValue: any = target[key];
let sourceValue: any = source[key];
const customizerOptions = {sourceValue, targetValue, key, source, target};
if (areObjects(sourceValue, targetValue)) {
return {
...acc as object,
[key]: !options.mergeObjects ?
immutableMerge(sourceValue, targetValue, options) : options.mergeObjects(customizerOptions),
};
}
if (areArrays(sourceValue, targetValue)) {
return {
...acc as object,
[key]: !options.mergeArrays ?
immutableArrayMerge(sourceValue, targetValue, options) : options.mergeArrays(customizerOptions),
};
}
return {
...acc as object,
[key]: !options.mergePrimitives ? targetValue : options.mergePrimitives(customizerOptions),
};
}, _.clone(source)) as TSrc & TMix;
}
export function immutableArrayMerge<TSrc extends any[]>(origin: any[], value: any[], options: IMergeOptions = {}): TSrc {
let index;
let result = [...origin];
for (index in value) {
if (value.hasOwnProperty(index) && /^\d+$/.test(index)) {
let replacement = value[index];
index = parseInt(index, 10);
if (areObjects(result[index], value[index])) {
replacement = immutableMerge(result[index], value[index], options);
}
if (areArrays(result[index], value[index])) {
replacement = immutableArrayMerge(result[index], value[index], options);
}
result = [
...result.slice(0, index),
replacement,
...result.slice(index + 1),
];
}
}
return result as TSrc;
}
export type TPasswordValidatorRules = {
length?: boolean; // min length
digit?: boolean; // must contain digits
twoCases?: boolean; // must contain different cases
}
export type TPasswordValidatorOptions = {
length?: number;
rules?: TPasswordValidatorRules;
}
const defaultOptions: TPasswordValidatorOptions = {
length: 6,
rules: {
length: true,
digit: true,
twoCases: true
}
};
/**
* Check passwords strength
*
* @example
* const validator = new PasswordValidator(); // PasswordValidator {rules: Object}
* validator.check("ы") // false
* validator.getState() // Object {length: false, digit: false, twoCases: false}
* validator.check("ы2") // false
* validator.getState() // Object {length: false, twoCases: false}
* validator.check("ы2qwff") // false
* validator.getState(true) // Object {length: true, digit: true, twoCases: false}
* validator.check("ы2qwffШШ") // true
* validator.getState() // Object {}
*/
export class PasswordValidator implements IStringValidator {
private rules: TPasswordValidatorRules = defaultOptions.rules;
check(password: string, options: TPasswordValidatorOptions = {}): boolean {
options = _.defaultsDeep(_.cloneDeep(options), defaultOptions);
this.reset();
const rules: TPasswordValidatorRules = {
length: !options.rules.length || this.checkLength(password, options.length),
digit: !options.rules.digit || this.checkDigit(password),
twoCases: !options.rules.twoCases || this.checkTwoCases(password)
};
return _.every(rules, (isPassed) => isPassed);
}
getState(onlyErrors: boolean = true): TPasswordValidatorRules {
if (onlyErrors) {
return _.omitBy(this.rules, (isPassed: boolean) => isPassed);
}
return {...this.rules}; // safe
}
private reset(): void {
this.rules = _.mapValues(this.rules, () => true);
}
private checkLength(password: string, length: number): boolean {
if (password.length < length) {
return this.rules.length = false;
}
return true;
}
private checkDigit(password: string): boolean {
if (password === password.replace(/\d/g, '')) {
return this.rules.digit = false;
}
return true;
}
private checkTwoCases(password: string): boolean {
const doesContainAlphaSymbols = password.replace(/\d/g, '') === password.replace(/[^\d]/g, '');
const doesContainUppercasedLetters = password.toLowerCase() === password;
return this.rules.twoCases = doesContainAlphaSymbols && doesContainUppercasedLetters;
}
}
export default PasswordValidator;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment