Skip to content

Commit

Permalink
Merge pull request #42 from jfrazx/feature/setSupport
Browse files Browse the repository at this point in the history
feat(set): handle sets
  • Loading branch information
jfrazx authored Dec 24, 2024
2 parents e54f021 + a8c5a68 commit 888094a
Show file tree
Hide file tree
Showing 22 changed files with 168 additions and 34 deletions.
6 changes: 3 additions & 3 deletions .github/workflows/tests.yml
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,17 @@ name: Tests

on:
push:
branches: [main, develop]
branches: [master, develop]
pull_request:
branches: [main, develop]
branches: [master, develop]

jobs:
build:
runs-on: ubuntu-latest

strategy:
matrix:
node-version: [16.x, 18.x, 20.x, 22.x, 24.x]
node-version: [16.x, 18.x, 20.x, 22.x]

steps:
- uses: actions/checkout@v4
Expand Down
2 changes: 2 additions & 0 deletions .npmignore
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ tsconfig.json
tslint.json
.eslintrc
.editorconfig
renovate.json
.travis.yml
.prettierrc
.releaserc
Expand All @@ -24,6 +25,7 @@ typings/
node_modules/
examples/
build-examples/
build/
spec/
test/
docs/
Expand Down
1 change: 1 addition & 0 deletions .nycrc.json
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
"src/factory/index.ts",
"src/defaults/index.ts",
"src/handlers/index.ts",
"src/handlers/wrap/index.ts",
"src/rules/defaults/index.ts",
"src/handlers/value/index.ts",
"src/handlers/value/handlers/index.ts"
Expand Down
2 changes: 1 addition & 1 deletion LICENSE
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
The MIT License (MIT)

Copyright (c) 2023 jfrazx
Copyright (c) 2025 jfrazx

Permission is hereby granted, free of charge, to any person obtaining a copy
of this software and associated documentation files (the "Software"), to deal
Expand Down
9 changes: 8 additions & 1 deletion cspell.json
Original file line number Diff line number Diff line change
@@ -1,8 +1,15 @@
{
"version": "0.2",
"language": "en",
"ignorePaths": ["node_modules", "dist", "coverage", "docs", "public"],
"ignorePaths": [
"node_modules",
"dist",
"coverage",
"docs",
"public"
],
"words": [
"automerge",
"codecov",
"deregisters",
"esnext",
Expand Down
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
},
"scripts": {
"test": "mocha",
"precompile": "rimraf dist",
"precompile": "rimraf build",
"compile": "tsc",
"build": "rimraf dist && parcel build",
"watch": "parcel watch",
Expand Down
1 change: 1 addition & 0 deletions src/handlers/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,2 @@
export * from './value';
export * from './wrap';
2 changes: 1 addition & 1 deletion src/handlers/value/base/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ import type { OptionsContainer } from '../../../options';
export abstract class ValueHandler<T extends object, TValue> implements IValueHandler<TValue> {
constructor(
protected readonly target: T,
protected readonly value: TValue,
public readonly value: TValue,
protected readonly options: OptionsContainer<T, TValue>,
) {}

Expand Down
13 changes: 8 additions & 5 deletions src/handlers/value/handlers/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@ import type { Property } from '../../../interfaces';
import { ValueHandler } from '../base';

/**
* @description Map value handler operators on
* @description Map value handler for managing map content
*
* @export
* @class MapValueHandler
Expand All @@ -22,13 +22,16 @@ export class MapValueHandler<T extends object, TValue> extends ValueHandler<T, M
this.options,
).supplyDefault(event);

const updatedKey: any = this.options.reuseMapKey
? key
: ValueHandlerRuleRunner.for<T, any>(this.target, key, this.options).supplyDefault(event);

const updatedKey = this.retrieveMapKey(key, event);
map.set(updatedKey, updatedValue);
}

return map;
}

private retrieveMapKey(key: T, event: Property): T {
return this.options.reuseMapKey
? key
: ValueHandlerRuleRunner.for<T, any>(this.target, key, this.options).supplyDefault(event);
}
}
29 changes: 16 additions & 13 deletions src/defaults/defaults.ts → src/handlers/wrap/defaults.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Property, IDefaults, IgnoreCriteria, IValueHandler } from '../interfaces';
import { isUndefined, isUnwrapDefaults, isObject } from '../helpers';
import type { OptionsContainer } from '../options';
import { criteria } from '../configuration';
import { isUndefined, isUnwrapDefaults, isObject } from '../../helpers';
import type { OptionsContainer } from '../../options';
import { criteria } from '../../configuration';
import type { Property, IDefaults, IValueHandler, IgnoreCriteria } from '../../interfaces';

/**
* @description Base handler for managing wrapped content
Expand All @@ -25,7 +25,8 @@ export class Defaults<T extends object = Record<string, any>, TValue = any>
return target;
}

get(target: T, event: Property): TValue {
// eslint-disable-next-line @typescript-eslint/no-unused-vars
get(target: T, event: Property, _receiver: T): TValue {
if (isUnwrapDefaults(event)) {
return this.unwrapDefaults.bind(this, target) as TValue;
}
Expand Down Expand Up @@ -83,18 +84,20 @@ export class Defaults<T extends object = Record<string, any>, TValue = any>
}

protected useCriteria(value: TValue | IgnoreCriteria<TValue>) {
let setValue = value as TValue;
let ignore = false;

if (this.shouldIgnore(value)) {
setValue = value.value;
ignore = value.ignoreDefaultCriteria;
if (this.isIgnoreCriteria(value)) {
return {
criteria,
setValue: value.value,
};
}

return { criteria: ignore ? criteria : this.options.setCriteria, setValue };
return {
criteria: this.options.setCriteria,
setValue: value,
};
}

protected shouldIgnore(value: any): value is IgnoreCriteria {
protected isIgnoreCriteria(value: any): value is IgnoreCriteria {
return isObject(value) && (value as IgnoreCriteria).ignoreDefaultCriteria !== undefined;
}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { isFunction, isUndefined } from '../helpers';
import { isFunction, isUndefined } from '../../helpers';
import type { TargetReceiver } from './interfaces';
import type { Property } from '../interfaces';
import type { Property } from '../../interfaces';
import { Defaults } from './defaults';

/**
Expand All @@ -19,7 +19,7 @@ export class DefaultsArray<T extends object, TValue = any> extends Defaults<T, T

return isFunction(original)
? (this.handle.bind(this, intendedTarget, event, original) as TValue)
: super.get(target, event);
: super.get(target, event, receiver!);
}

protected getTarget({ receiver }: TargetReceiver<T>): T {
Expand Down
11 changes: 10 additions & 1 deletion src/defaults/defaultsMap.ts → src/handlers/wrap/defaultsMap.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,16 @@
import type { TargetReceiver } from './interfaces';
import type { Property } from '../../interfaces';
import { DefaultsArray } from './defaultsArray';
import type { Property } from '../interfaces';

/**
* @description Handler for wrapped Maps
*
* @export
* @class DefaultsMap
* @extends {DefaultsArray<Map<T, TValue>, TValue>}
* @template T
* @template TValue
*/
export class DefaultsMap<T extends object, TValue = any> extends DefaultsArray<Map<T, TValue>, TValue> {
protected getTarget({ target }: TargetReceiver<Map<T, TValue>>): Map<T, TValue> {
return target;
Expand Down
37 changes: 37 additions & 0 deletions src/handlers/wrap/defaultsSet.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
import type { TargetReceiver } from './interfaces';
import type { Property } from '../../interfaces';
import { DefaultsArray } from './defaultsArray';
import { isUndefined } from '../../helpers';

/**
* @note Sets are not really value retrieving objects. Support is enabled to ensure they function normally if received by defaults
*
* @export
* @class DefaultsSet
* @extends {DefaultsArray<Set<TValue>, TValue>}
* @template TValue
*/
export class DefaultsSet<TValue = any> extends DefaultsArray<Set<TValue>, TValue> {
get(target: Set<TValue>, event: Property, receiver?: Set<TValue>): TValue {
if (event === 'size') {
return Reflect.get(receiver!, event, target) as TValue;
}

if (event === 'add') {
return this.handleAdd.bind(this, target, event) as TValue;
}

return super.get(target, event, receiver!);
}

private handleAdd(target: Set<TValue>, event: Property, value: TValue): TValue {
const shouldUseDefault =
isUndefined(value) || this.options.setCriteria.call(target, value, event, target);
const useValue = shouldUseDefault ? this.value.supplyDefault(event) : value;
return target.add(useValue) as TValue;
}

protected getTarget({ target }: TargetReceiver<Set<TValue>>): Set<TValue> {
return target;
}
}
1 change: 1 addition & 0 deletions src/defaults/index.ts → src/handlers/wrap/index.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
export * from './defaults';
export * from './defaultsArray';
export * from './defaultsMap';
export * from './defaultsSet';
File renamed without changes.
3 changes: 2 additions & 1 deletion src/interfaces/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ export type Default<WrappedObject extends object = Record<string, any>> = Wrappe
Unwrap<WrappedObject>;

export interface IDefaults<WrappedObject, DefaultValue> {
get(target: WrappedObject, event: Property): DefaultValue;
get(target: WrappedObject, event: Property, receiver: WrappedObject): DefaultValue;
set(target: WrappedObject, property: Property, value: DefaultValue): boolean;
unwrapDefaults(target: WrappedObject): WrappedObject;
}
Expand Down Expand Up @@ -142,5 +142,6 @@ export type ExecuteFunction<DefaultValue> =
};

export interface IValueHandler<DefaultValue> {
value: DefaultValue;
supplyDefault(event: Property): DefaultValue;
}
2 changes: 1 addition & 1 deletion src/rules/defaults/rules/array.rule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IDefaults } from '../../../interfaces';
import { DefaultsArray } from '../../../defaults';
import { DefaultsArray } from '../../../handlers';
import { DefaultRule } from '../base';

export class ArrayRule<T extends object, TValue> extends DefaultRule<T, TValue> {
Expand Down
2 changes: 1 addition & 1 deletion src/rules/defaults/rules/defaults.rule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IDefaults } from '../../../interfaces';
import { Defaults } from '../../../defaults';
import { Defaults } from '../../../handlers';
import { DefaultRule } from '../base';

export class DefaultsRule<T extends object, TValue> extends DefaultRule<T, TValue> {
Expand Down
3 changes: 2 additions & 1 deletion src/rules/defaults/rules/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,8 @@ import type { DefaultRuleConstruct } from '../../interfaces';
import { DefaultsRule } from './defaults.rule';
import { ArrayRule } from './array.rule';
import { MapRule } from './map.rule';
import { SetRule } from './set.rule';

export const getDefaultsRules = <T extends object, TValue>(): DefaultRuleConstruct<T, TValue>[] => {
return [ArrayRule, MapRule, DefaultsRule] as any;
return [ArrayRule, MapRule, SetRule, DefaultsRule] as any;
};
2 changes: 1 addition & 1 deletion src/rules/defaults/rules/map.rule.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import type { IDefaults } from '../../../interfaces';
import { DefaultsMap } from '../../../defaults';
import { DefaultsMap } from '../../../handlers';
import { DefaultRule } from '../base';

export class MapRule<T extends object, TValue> extends DefaultRule<Map<T, TValue>, TValue> {
Expand Down
13 changes: 13 additions & 0 deletions src/rules/defaults/rules/set.rule.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
import type { IDefaults } from '../../../interfaces';
import { DefaultsSet } from '../../../handlers';
import { DefaultRule } from '../base';

export class SetRule<TValue> extends DefaultRule<Set<TValue>, TValue> {
shouldHandle(): boolean {
return this.wrap instanceof Set;
}

handle(): IDefaults<Set<TValue>, TValue> {
return new DefaultsSet<TValue>(this.options, this.valueHandler);
}
}
55 changes: 55 additions & 0 deletions test/set.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,55 @@
import { wrapDefaults } from '../src/';
import { expect } from 'chai';

describe('Set', () => {
it('should use a set as a wrapped value', () => {
const set = new Set<number>();

const wrapped = wrapDefaults({
wrap: set,
defaultValue: 7,
});

expect(wrapped).to.be.instanceOf(Set);

expect(wrapped.has(9)).to.be.false;
expect(wrapped.size).to.equal(0);
});

it('should not set primitive values when undefined', () => {
const set = new Set<number>();

const wrapped = wrapDefaults({
wrap: set,
defaultValue: 7,
setUndefined: true,
});

expect(wrapped.has(7)).to.be.false;
expect(wrapped.size).to.equal(0);

wrapped.add(<any>undefined);

expect(wrapped.has(7)).to.be.true;
expect(wrapped.size).to.equal(1);
expect(wrapped.has(<any>undefined)).to.be.false;
});

it('should use setCriteria', () => {
const set = new Set<number>();

const wrapped = wrapDefaults({
wrap: set,
defaultValue: 4,
setCriteria: (v) => v > 10,
});

wrapped.add(11);
wrapped.add(-9);

expect(wrapped.has(4)).to.be.true;
expect(wrapped.has(11)).to.be.false;
expect(wrapped.has(-9)).to.be.true;
expect(wrapped.size).to.equal(2);
});
});

0 comments on commit 888094a

Please sign in to comment.