Skip to content

Commit

Permalink
ESM tests run in the browser
Browse files Browse the repository at this point in the history
  • Loading branch information
barneycarroll committed Aug 10, 2018
1 parent 2b23814 commit 7ecb8dd
Show file tree
Hide file tree
Showing 8 changed files with 191 additions and 30 deletions.
50 changes: 29 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,9 +5,9 @@ A tool for making deep & subtle mutations on - or modified copies of - Javascrip
Throw your rose-tinted [lenses](https://medium.com/javascript-inside/an-introduction-into-lenses-in-javascript-e494948d1ea5), [reducers](http://redux.js.org/docs/basics/Reducers.html) & [decorators](https://tc39.github.io/proposal-decorators/) out the window: Patchinko is an ECMAScript3-compliant utility that makes complex patching fast and easy, without the ceremony.

* [What?](#what)
* API variations:
* [Explicit](#explicit)
* [Constant](#1-constant)
* [Explicit](#explicit) or
* [Overloaded](#explicit):
* [Constant](#1-constant) or
* [Immutable](#2-immutable)
* [Where?](#where)
* [How?](#how)
Expand All @@ -21,26 +21,21 @@ Throw your rose-tinted [lenses](https://medium.com/javascript-inside/an-introduc

Patchinko exposes 4 explicit APIs: `P`, `S`, `PS`, & `D`. In general it's easier to work with the overloaded APIs, but explicit is instructive in getting a clear mental model of the different granular operations Patchinko performs under the hood.

`P` is like `Object.assign`: given `P(target, input1, input2, etc)`, it consumes inputs left to right and copies their properties onto the supplied target

*…except that…*

If any target properties are instances of `S(function)`, it will supply the scoped function with the target property for that key, and assign the result back to the target.

If any target properties are `D`, it will delete the property of the same key on the target.

`PS([ target, ] input)` is a composition of `P` & `S`, for when you need to patch recursively. If you supply a `target`, the original value will be left untouched (useful for immutable patching).
* `P` is like `Object.assign`: given `P(target, input1, input2, etc)`, it consumes inputs left to right and copies their properties onto the supplied target, *except that:*
* If any target properties are instances of `S(function)`, it will supply the scoped function with the target property for that key, and assign the result back to the target;
* If any target properties are `D`, it will delete the property of the same key on the target;
* `PS([ target, ] input)` is a composition of `P` & `S`, for when you need to patch recursively. If you supply a `target`, the original value will be left untouched (useful for immutable patching).

## Overloaded

Patchinko also comes with a don't-make-me-think single-reference overloaded API - useful when the essential patching operations are intuitive but the different API invocations are cognitively overbearing to determine or noisy to read.

`O` is an overloaded API that subsumes the above (with the exception of the n-ary immutable `PS` overload):

1. No arguments stands in for `D`.
2. A function argument stands in for `S`.
3. A non-function single argument stands in for `PS`.
4. …otherwise, `P`.
* No arguments stands in for `D`
* A function argument stands in for `S`
* A non-function single argument stands in for `PS`
* …otherwise, `P`

The overloaded API comes in 2 flavours:

Expand All @@ -53,14 +48,14 @@ The 1st variation of the overloaded API assumes you want to mutate the `target`s
The 2nd works on a more functional basis: the `target`s of each operation are left intact and any changes result in new objects being produced as the result of each operation. This is the immutable approach.

> #### ☝️ Why does it matter?
>
> If you're using Patchinko to monkey-patch an arbitrary third party API, you almost certainly want to mutate it: complex APIs may use 'instanceof' and equality reference checks internally; if you're patching a class / prototypal construct with internal and external references across the code-base, you need to preserve those references in order for everything to work as expected.
>
> But if you're using Patchinko to make changes to a data structure that's the sole business of your application's data model, that kind of stuff shouldn't be necessary - you can and should certainly avoid those patterns (they're complex and brittle!). In this scenario, creating new objects instead of mutating old ones can make the development & debugging process significantly easier:
>
> * Because the result of each patch operation is a new entity, you can store the results as new references and compare them later on. This can be useful when you want to see how a model has changed step by step over the course of several operations.
> * Because nested structures within the patched entity that *haven't* been individually patched will retain their old identity, you an use [memoization](https://en.wikipedia.org/wiki/Memoization) to avoid unnecessary reactive computations. Traditionally this has been touted as a method for reactive Javascript applications - in particular virtual DOM libraries like [Mithril](https://mithril.js.org/) - to increase performance by skipping wasteful recomputations; but the salient advantage of this functionality is for debugging - you can set breakpoints far downstream in an application call graph and only pause script execution if and when change has occured.
>
> *When it comes to any defensive 'best practice' for the sake of performance - in the absence of any qualifiable evidence - the ability for authors & readers to reason & interact with the code lucidly should always be more jusdged more important to the architecture of code than any theories about what the computer might prefer.*
# Where?
Expand All @@ -71,19 +66,27 @@ In Node:

```js
const {P, S, PS, D} = require('patchinko/explicit')

// or

const O = require('patchinko/constant')

// or

const O = require('patchinko/immutable')
```

With ESM:

```mjs
```js
import {P, S, PS, D} from 'patchinko/explicit'

// or

import O from 'patchinko/constant'

// or

import O from 'patchinko/immutable'
```

Expand All @@ -92,10 +95,14 @@ In the browser:
```html
<script src=//unpkg.com/patchinko@4.1.0/explicit.mjs></script>
<script>console.log({P, S, PS, D})</script>

<!-- or -->

<script src=//unpkg.com/patchinko@4.1.0/overloaded.mjs></script>
<script>console.log({O})</script>

<!-- or -->

<script src=//unpkg.com/patchinko@4.1.0/immutable.mjs></script>
<script>console.log({O})</script>
```
Expand Down Expand Up @@ -304,6 +311,7 @@ Bear in mind you can't return `P`, `PS`, or `D` operations from `S`. This is nev
* ECMAScript modules
* `overloaded` renamed to `constant`
* All API variants exposed via entry point
* Browser-based ESM tests (`.html` files in tests folder)
* Refactor tests to avoid symbols (they're unnecessary and misleading)
* Troubleshooting documentation (+ tweaks)
* Updated dependencies (+ API compliance tweaks)
Expand Down
6 changes: 3 additions & 3 deletions index.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ try {
var explicit = require('./explicit')

module.exports = {
P : explicit.D,
P : explicit.P,
S : explicit.S,
PS : explicit.PS,
D : explicit.D,
Expand All @@ -13,8 +13,8 @@ try {
catch(e){
throw new Error(
'The environment attempted to load all Patchinko APIs via CommonJS, '
+ 'but the environment doesn\'t support the CommonJS API. \n'
+ 'You probably need to fix a script tag to point to the specific file you need. \n'
+ 'but the environment doesn\'t appear to support CommonJS - '
+ 'you probably need to fix a script tag to point to the specific file you need. \n'
+ 'For install & import instructions, see: \n'
+ 'https://github.com/barneycarroll/patchinko/blob/master/README.md#where \n'
+ 'Original error: \n'
Expand Down
45 changes: 45 additions & 0 deletions tests/constant.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
<!doctype html>
<html>
<head>
<meta charset=utf-8>

<title>
Patchinko test suite: constant.mjs
</title>

<script src=../node_modules/ospec/ospec.js></script>

<script>
const require = () => { throw '' }
</script>

<script type=module>
import('../constant.mjs').then(x => {
window.O = x.default

import('./constant.js').then(() => {
setTimeout(
() => {
o.run()

notice.innerHTML = 'Open the console for test results'
},
1000,
)
})
})
</script>
</head>

<body style=font-family:sans-serif>
<p>
<a href="./">./</a>
</p>

<h1>
Patchinko test suite: <code>constant.mjs</code>
</h1>

<p id=notice>Wait for it...</p>
</body>
</html>
8 changes: 6 additions & 2 deletions tests/constant.js
Original file line number Diff line number Diff line change
@@ -1,10 +1,14 @@
const o = require('ospec')
try {
var o = require('ospec')

const O = require('../constant.js')
var O = require('../constant.js')
}
catch(e){}

const I = x => x
const A = f => x => f(x)


o.spec('Mutable overload API: ', () => {
o('`O` (with a single function argument)', () => {
const unique = {}
Expand Down
50 changes: 50 additions & 0 deletions tests/explicit.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
<!doctype html>
<html>

<head>
<meta charset=utf-8>

<title>
Patchinko test suite: explicit.mjs
</title>

<script src=../node_modules/ospec/ospec.js></script>

<script>
const require = () => { throw '' }
</script>

<script type=module>
import('../explicit.mjs').then(x => {
const {P, S, PS, D} = x

Object.assign(window, {P, S, PS, D})

import('./explicit.js').then(() => {
setTimeout(
() => {
o.run()

notice.innerHTML = 'Open the console for test results'
},
1000,
)
})
})
</script>
</head>

<body style=font-family:sans-serif>
<p>
<a href="./">./</a>
</p>

<h1>
Patchinko test suite:
<code>explicit.mjs</code>
</h1>

<p id=notice>Wait for it...</p>
</body>

</html>
7 changes: 5 additions & 2 deletions tests/explicit.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const o = require('ospec')
try {
var o = require('ospec')

const {P, S, PS, D} = require('../explicit.js')
var {P, S, PS, D} = require('../explicit.js')
}
catch(e){}

const I = x => x
const A = f => x => f(x)
Expand Down
48 changes: 48 additions & 0 deletions tests/immutable.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
<!doctype html>
<html>

<head>
<meta charset=utf-8>

<title>
Patchinko test suite: immutable.mjs
</title>

<script src=../node_modules/ospec/ospec.js></script>

<script>
const require = () => { throw '' }
</script>

<script type=module>
import('../immutable.mjs').then(x => {
window.O = x.default

import('./immutable.js').then(() => {
setTimeout(
() => {
o.run()

notice.innerHTML = 'Open the console for test results'
},
1000,
)
})
})
</script>
</head>

<body style=font-family:sans-serif>
<p>
<a href="./">./</a>
</p>

<h1>
Patchinko test suite:
<code>immutable.mjs</code>
</h1>

<p id=notice>Wait for it...</p>
</body>

</html>
7 changes: 5 additions & 2 deletions tests/immutable.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,9 @@
const o = require('ospec')
try {
var o = require('ospec')

const O = require('../immutable.js')
var O = require('../immutable.js')
}
catch(e){}

const I = x => x
const A = f => x => f(x)
Expand Down

0 comments on commit 7ecb8dd

Please sign in to comment.