-
Notifications
You must be signed in to change notification settings - Fork 5
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
resolves #24 Tested and linted typescript definitions for TS ⩾ 3.5. Run tests with `npm run test:ts` which uses dtslint in the background. The root of typings are in types/patchinko, a requirement from dtslint. The most important tests are located in __tests__/explicit and __tests__/overlaoded. One big feature of those typings is to strictly enforce patchinko rules. As such, every do and don't from the readme has been carefuly tested and implemented. One limitation of those types however, is that the returned type of a P(target, ...) call will always be typeof target. Although it could be possible, in theory, to find the resulting type from operations such as deletions ore scoped replacements, I have found this incredibly difficult and wasn't certain the compiler could follow this level of complexity and computational burden. Therefore, I thought it would be an acceptable compromise to return the target type, given the vast majority of use-cases where one want such outcome. On the test side, dtslint does the following: - Lint the sources according to DefinitelyTyped guidelines; - Run any test file and check for $ExpectType and $ExpectError instructions, report compilation errors; - Run one batch of test for each TypeScript supported version.
- Loading branch information
Showing
14 changed files
with
1,295 additions
and
102 deletions.
There are no files selected for viewing
Large diffs are not rendered by default.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -4,15 +4,22 @@ | |
"description": "A concise tool for declarative object manipulation", | ||
"main": "index.js", | ||
"module": "index.mjs", | ||
"types": "types/patchinko/index.d.ts", | ||
"repository": "[email protected]:barneycarroll/patchinko.git", | ||
"author": "Barney Carroll <[email protected]>", | ||
"license": "MIT", | ||
"devDependencies": { | ||
"conditional-type-checks": "^1.0.5", | ||
"dts-gen": "^0.5.7", | ||
"eslint": "^5.15.1", | ||
"ospec": "3.0.1" | ||
}, | ||
"scripts": { | ||
"test": "ospec && eslint ." | ||
"test": "npm run test:lint . && npm run test:ts", | ||
"test:lint": "ospec && eslint", | ||
"test:ts": "dtslint types/patchinko" | ||
}, | ||
"dependencies": { | ||
"dtslint": "^2.0.5" | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,84 @@ | ||
import { P, PS, D, S } from "patchinko"; | ||
|
||
// Integration tests | ||
|
||
interface Bar { | ||
bish: string; | ||
} | ||
|
||
interface X { | ||
foo?: { | ||
bar: Bar; | ||
oop?: string | ||
}; | ||
z: string; | ||
} | ||
|
||
const x: X = { | ||
foo: { bar: { bish: "bish" } }, | ||
z: 'zoo' | ||
}; | ||
|
||
// Correct | ||
// $ExpectType X | ||
P(x, { foo: D }); | ||
|
||
// Correct | ||
// $ExpectType X | ||
P(x, { foo: PS({ bar: D }) }); | ||
|
||
// Correct | ||
// $ExpectType X | ||
P(x, { foo: PS({ oop: D, bar: { bish: "bash" } }) }); | ||
|
||
// Correct, not all properties are required as a PS patch argument. | ||
// $ExpectType X | ||
P(x, { foo: PS({ oop: D }) }); | ||
|
||
// Incorrect, we are adding an unknown property to X['foo']. | ||
// $ExpectError | ||
P(x, { foo: PS({ tada: "" }) }); | ||
|
||
// Correct | ||
// $ExpectType X | ||
P(x, { foo: PS({ bar: PS({ bish: "bash" }) }) }); | ||
|
||
// Also correct - but `bar` will not be patched - instead it will be replaced. | ||
// $ExpectType X | ||
P(x, { foo: PS({ bar: { bish: "bash" } }) }); | ||
|
||
// Correct usage of S | ||
// $ExpectType X | ||
P(x, { foo: S((old: X['foo']) => ({ bar: { bish: (old && old.bar.bish || '') + 'bash' } }))}); | ||
|
||
// Correct usage of S nested in a PS call | ||
// $ExpectType X | ||
P(x, { foo: PS({ bar: S(() => ({ bish: 'oops' })) }) }); | ||
|
||
// Incorrect - we can't patch `bar` because its container - `foo` is a wholesale replacement: | ||
// $ExpectError | ||
P(x, PS({ foo: { bar: PS({ bish: "bash" }) } })); | ||
|
||
// Incorrect - primitive values cannot be patched | ||
// $ExpectError | ||
P(x, { foo: PS({ bar: PS({ bish: PS("bash") }) }) }); | ||
|
||
// Incorrect - wrapping is only necessary for child structures - patch arguments will always patch, not replace | ||
// $ExpectError | ||
P(x, PS({ foo: PS({ bar: PS({ bish: "bash" }) }) })); | ||
|
||
// Incorrect, D must not be called | ||
// $ExpectError | ||
P(x, { foo: D() }); | ||
|
||
// Incorrect usage of D nested in a S patch | ||
// $ExpectError | ||
P(x, { foo: S(() => ({ bar: D })) }); | ||
|
||
// Correct usage of PS second signature | ||
// $ExpectType X | ||
P(x, { foo: PS({}, { bar: { bish: "bash" }}) }); | ||
|
||
// Incorrect, we are adding an unknown property to X['foo']. | ||
// $ExpectError | ||
P(x, { foo: PS({}, { tada: 'bah' }) }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,46 @@ | ||
import { assert, IsExact, IsNever } from "conditional-type-checks"; | ||
import { PSInstruction, FromPatchRequest, DInstruction, SInstruction } from 'patchinko'; | ||
import { Target, InnerTarget } from "./targets"; | ||
|
||
interface DeepType { | ||
a: { | ||
b: Target | ||
}; | ||
} | ||
|
||
interface DeeplyNestedInstructions { | ||
a: PSInstruction<{ | ||
b: PSInstruction<Target>; | ||
}>; | ||
} | ||
|
||
interface BaseType { | ||
a: Target; | ||
} | ||
|
||
interface WithDeleteInstructions { | ||
a: DInstruction<Target>; | ||
} | ||
|
||
interface WithScopeInstructions { | ||
a: SInstruction<Target>; | ||
} | ||
|
||
interface WithOrphanedInstructions { | ||
t: { | ||
// Cannot patch `c` because its container `t` is a substitution. | ||
c: PSInstruction<InnerTarget> | ||
}; | ||
} | ||
|
||
// WithOrphanedInstruction should not be candidate for extraction. | ||
assert<IsNever<FromPatchRequest<WithOrphanedInstructions>>>(true); | ||
|
||
// Extracting from WithDeleteInstructions should result in BaseType. | ||
assert<IsExact<BaseType, FromPatchRequest<WithDeleteInstructions>>>(true); | ||
|
||
// Extracting from WithScopeInstructions should result in BaseType. | ||
assert<IsExact<BaseType, FromPatchRequest<WithScopeInstructions>>>(true); | ||
|
||
// Extracting from DeeplyNestedInstructions should result in DeepType. | ||
assert<IsExact<DeepType, FromPatchRequest<DeeplyNestedInstructions>>>(true); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,26 @@ | ||
import { P, PS } from 'patchinko'; | ||
import { assert, IsExact } from 'conditional-type-checks'; | ||
import { Target } from './targets'; | ||
|
||
// Handling of instances from arbitrary classes | ||
|
||
interface WithSet { | ||
a: Set<Target>; | ||
} | ||
|
||
interface WithArray { | ||
b: Target[]; | ||
} | ||
|
||
declare const withSet: WithSet; | ||
declare const withArray: WithArray; | ||
|
||
const test1 = P(withSet, { a: new Set<Target>() }); | ||
assert<IsExact<WithSet, typeof test1>>(true); | ||
|
||
// Monkey patching | ||
const test2 = P(withSet, { a: PS({ clear: () => {} })}); | ||
assert<IsExact<WithSet, typeof test2>>(true); | ||
|
||
const test3 = P(withArray, { b: [] }); | ||
assert<IsExact<WithArray, typeof test3>>(true); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,13 @@ | ||
import { NeverAscend } from "patchinko"; | ||
import { assert, IsNever } from "conditional-type-checks"; | ||
|
||
assert<IsNever<NeverAscend<{}>>>(false); | ||
assert<IsNever<NeverAscend<{ a: never }>>>(true); | ||
assert<IsNever<NeverAscend<{ a: never; b: string }>>>(true); | ||
assert<IsNever<NeverAscend<{ a: number; b: string }>>>(false); | ||
assert<IsNever<NeverAscend<{ a: any }>>>(false); | ||
assert<IsNever<NeverAscend<{ a: unknown }>>>(false); | ||
assert<IsNever<NeverAscend<{ a: undefined }>>>(false); | ||
assert<IsNever<NeverAscend<{ a: string }>>>(false); | ||
assert<IsNever<NeverAscend<{ a?: string }>>>(false); | ||
assert<IsNever<NeverAscend<{ a?: never }>>>(true); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,72 @@ | ||
import { immutable as O, D, WholesomeReplacement, DSymbol, Overloaded, DInstruction, SPatchFunction } from "patchinko"; | ||
|
||
// Integration tests | ||
|
||
interface Bar { | ||
bish: string; | ||
} | ||
|
||
interface X { | ||
foo?: { | ||
bar: Bar; | ||
oop?: string | ||
}; | ||
z: string; | ||
} | ||
|
||
const x: X = { | ||
foo: { bar: { bish: "bish" } }, | ||
z: 'zoo' | ||
}; | ||
|
||
// Correct | ||
// $ExpectType X | ||
O(x, { foo: O }); | ||
|
||
// Correct | ||
// $ExpectType X | ||
O(x, { foo: O({ bar: O }) }); | ||
|
||
// Correct | ||
// $ExpectType X | ||
O(x, { foo: O({ oop: O, bar: { bish: "bash" } }) }); | ||
|
||
// Correct, not all properties are required as a O patch argument. | ||
// $ExpectType X | ||
O(x, { foo: O({ oop: O }) }); | ||
|
||
// Correct | ||
// $ExpectType X | ||
O(x, { foo: O({ bar: O({ bish: "bash" }) }) }); | ||
|
||
// Also correct - but `bar` will not be patched - instead it will be replaced. | ||
// $ExpectType X | ||
O(x, { foo: O({ bar: { bish: "bash" } }) }); | ||
|
||
// Correct usage of O | ||
// $ExpectType X | ||
O(x, { foo: O((old: X['foo']) => ({ bar: { bish: (old && old.bar.bish || '') + 'bash' } }))}); | ||
|
||
// Correct usage of O nested in a O call | ||
// $ExpectType X | ||
O(x, { foo: O({ bar: O(() => ({ bish: 'oops' })) }) }); | ||
|
||
// Incorrect - we can't patch `bar` because its container - `foo` is a wholesale replacement: | ||
// $ExpectError | ||
O(x, O({ foo: { bar: O({ bish: "bash" }) } })); | ||
|
||
// Incorrect - primitive values cannot be patched | ||
// $ExpectError | ||
O(x, { foo: O({ bar: O({ bish: O("bash") }) }) }); | ||
|
||
// Incorrect - wrapping is only necessary for child structures - patch arguments will always patch, not replace | ||
// $ExpectError | ||
O(x, O({ foo: O({ bar: O({ bish: "bash" }) }) })); | ||
|
||
// Incorrect, O cannot be called with no argument | ||
// $ExpectError | ||
O(x, { foo: O() }); | ||
|
||
// Incorrect usage of O nested in a O patch | ||
// $ExpectError | ||
O(x, { foo: O(() => ({ bar: O })) }); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,44 @@ | ||
import { assert, IsNever, NotHas, Has } from "conditional-type-checks"; | ||
import { PatchRequestOf, PSInstruction, DInstruction, P } from 'patchinko'; | ||
import { Target, InnerTarget } from "./targets"; | ||
|
||
interface Original { | ||
t: { | ||
c: InnerTarget | ||
}; | ||
} | ||
|
||
interface RequestWithOrphanedInstruction { | ||
t: { | ||
// Cannot patch `c` because its container `t` is a substitution. | ||
c: PSInstruction<InnerTarget> | ||
}; | ||
} | ||
|
||
// RequestWithOrphanedInstruction should not extend patch request. | ||
assert<NotHas<RequestWithOrphanedInstruction, PatchRequestOf<Original>>>(true); | ||
|
||
// A p patch request of InnerTarget should contain InnerTarget. | ||
assert<Has<InnerTarget, PatchRequestOf<InnerTarget>>>(true); | ||
|
||
// A p patch request of Target should contain Target. | ||
assert<Has<Target, PatchRequestOf<Target>>>(true); | ||
|
||
// Patch requests of primitives should result in never. | ||
assert<IsNever<PatchRequestOf<string>>>(true); | ||
assert<IsNever<PatchRequestOf<number>>>(true); | ||
assert<IsNever<PatchRequestOf<symbol>>>(true); | ||
assert<IsNever<PatchRequestOf<null>>>(true); | ||
assert<IsNever<PatchRequestOf<undefined>>>(true); | ||
assert<IsNever<PatchRequestOf<bigint>>>(true); | ||
|
||
interface OptionalTarget { | ||
target?: Target; | ||
} | ||
|
||
interface DeleteRequest { | ||
target: DInstruction<Target>; | ||
} | ||
|
||
// DeleteRequest should extend patch request of OptionalTarget. | ||
assert<Has<DeleteRequest, PatchRequestOf<OptionalTarget>>>(true); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
import { PSInstruction } from "patchinko"; | ||
|
||
export interface InnerTarget { | ||
a: string; | ||
} | ||
|
||
export interface Target { | ||
a: string; | ||
b: number; | ||
c: InnerTarget; | ||
} | ||
|
||
export interface DeeplyNestedTarget { | ||
a: { | ||
b: { | ||
c: Target | ||
} | ||
}; | ||
} | ||
|
||
export interface DeeplyNestedRequest { | ||
a: PSInstruction<{ | ||
b: PSInstruction<{ | ||
c: PSInstruction<Target> | ||
}> | ||
}>; | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,19 @@ | ||
import { assert, IsExact, IsNever } from "conditional-type-checks"; | ||
import { ToPatchRequest } from 'patchinko'; | ||
import { Target, InnerTarget } from "./targets"; | ||
|
||
type T = ToPatchRequest<InnerTarget>; | ||
|
||
// InnerTarget should be coercible to patch requests. | ||
assert<IsExact<ToPatchRequest<InnerTarget>, InnerTarget>>(true); | ||
|
||
// Target should be coercible to patch requests. | ||
assert<IsExact<ToPatchRequest<Target>, Target>>(true); | ||
|
||
// Primitives cannot be coerced to patch requests. | ||
assert<IsNever<ToPatchRequest<string>>>(true); | ||
assert<IsNever<ToPatchRequest<number>>>(true); | ||
assert<IsNever<ToPatchRequest<symbol>>>(true); | ||
assert<IsNever<ToPatchRequest<null>>>(true); | ||
assert<IsNever<ToPatchRequest<undefined>>>(true); | ||
assert<IsNever<ToPatchRequest<bigint>>>(true); |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,16 @@ | ||
import { assert, IsNever, IsExact } from "conditional-type-checks"; | ||
import { PSInstruction, WholesomeReplacement, DInstruction, Overloaded } from "patchinko"; | ||
import { Target, InnerTarget } from "./targets"; | ||
|
||
interface NestedPSInstruction { | ||
b: PSInstruction<Target>; | ||
} | ||
|
||
assert<IsExact<WholesomeReplacement<string>, string>>(true); | ||
assert<IsExact<WholesomeReplacement<InnerTarget>, InnerTarget>>(true); | ||
assert<IsExact<WholesomeReplacement<Target>, Target>>(true); | ||
assert<IsNever<WholesomeReplacement<PSInstruction<Target>>>>(true); | ||
assert<IsNever<WholesomeReplacement<NestedPSInstruction>>>(true); | ||
assert<IsNever<WholesomeReplacement<{ a: NestedPSInstruction }>>>(true); | ||
assert<IsNever<WholesomeReplacement<{ a: DInstruction<undefined> }>>>(true); | ||
assert<IsNever<WholesomeReplacement<{ a: Overloaded }>>>(true); |
Oops, something went wrong.