Skip to content

Commit

Permalink
Implemented updaters
Browse files Browse the repository at this point in the history
  • Loading branch information
mweststrate committed Dec 24, 2018
1 parent a9dfbb9 commit 2c6e360
Show file tree
Hide file tree
Showing 4 changed files with 251 additions and 31 deletions.
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -155,7 +155,7 @@ Todo:
* [ ] strict mode: only reads from actions or reactions. Only updates from actions.
* [ ] verify callign actions in reactions work correctly
* [ ] contributing and debugging
* [ ] updaters `inc1`, `inc`, `push`, `set`, `delete`, `assign`, `toggle`
* [x] updaters `inc1`, `inc`, `push`, `set`, `delete`, `assign`, `toggle`
* [ ] utils `assignVals`, `toJS`
* [ ] docs
* [ ] implement `SubscribeOptions`
Expand All @@ -169,6 +169,7 @@ Todo:
* [ ] add (mobx like) performance tests
* [ ] kill with-immmer?
* [ ] `sub`, pass in previous value as second argumetn
* [ ] improve updaters typings

Later
* [ ] eliminate classes from code base
Expand Down
54 changes: 41 additions & 13 deletions pkgs/updaters/package.json
Original file line number Diff line number Diff line change
@@ -1,19 +1,19 @@
{
"name": "@r-val/immer",
"name": "@r-val/updaters",
"private": false,
"version": "0.0.1",
"description": "",
"main": "dist/immer.js",
"umd:main": "dist/immer.umd.js",
"unpkg": "dist/immer.umd.js",
"module": "dist/immer.mjs",
"jsnext:main": "dist/immer.mjs",
"react-native": "dist/immer.mjs",
"main": "dist/updaters.js",
"umd:main": "dist/updaters.umd.js",
"unpkg": "dist/updaters.umd.js",
"module": "dist/updaters.mjs",
"jsnext:main": "dist/updaters.mjs",
"react-native": "dist/updaters.mjs",
"browser": {
"./dist/immer.js": "./dist/immer.js",
"./dist/immer.mjs": "./dist/immer.mjs"
"./dist/updaters.js": "./dist/updaters.js",
"./dist/updaters.mjs": "./dist/updaters.mjs"
},
"source": "immer.ts",
"source": "updaters.ts",
"scripts": {},
"author": "Michel Weststrate",
"license": "MIT",
Expand All @@ -28,7 +28,21 @@
},
"reserved": [
"toggle",
"inc1", "inc", "dec", "dec1", "replace", "set", "remove", "push", "shift", "unshift", "splice", "assign"
"inc1",
"inc",
"dec",
"dec1",
"replace",
"set",
"unset",
"push",
"shift",
"unshift",
"splice",
"assign",
"pop",
"removeBy",
"removeValue"
],
"mangle": {
"reserved": [
Expand All @@ -50,8 +64,22 @@
"effect",
"configure",
"autoFreeze",
"updater",
"produce"
"toggle",
"inc1",
"inc",
"dec",
"dec1",
"replace",
"set",
"unset",
"push",
"shift",
"unshift",
"splice",
"assign",
"pop",
"removeBy",
"removeValue"
]
}
}
150 changes: 148 additions & 2 deletions pkgs/updaters/tests/updaters.spec.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import { val, drv, sub, act } from "@r-val/core"
import { toggle, inc, dec1, inc1, dec } from "@r-val/updaters"
import { val, drv, sub, act, deepfreeze } from "@r-val/core"
import { toggle, inc, dec1, inc1, dec, replace, set, push, splice, shift, unshift, pop, assign, unset, removeBy, removeValue } from "@r-val/updaters"

test('toggle', () => {
const x = val(false)
Expand All @@ -21,4 +21,150 @@ test("inc/ dec", () => {
expect(x()).toBe(8)
x(dec(4))
expect(x()).toBe(4)
})

test("replace", () => {
const inc = i => i + 1

const a = val(3)
a(inc)
expect(a()).toBe(4)

const b = val<() => number>(() => 3)
const fn = function anotherFunction() {
return 4
}
b(() => fn)
expect(b()).toBe(fn)
expect(b()()).toBe(4)

b(replace(() => 5))
expect(b()()).toBe(5)
})

test("set", () => {
const ar = [1,2]
const obj = {
a: 1,
b: 2
}

expect(set("b", 3)(obj)).toEqual({ a: 1, b: 3})
expect(set("b", 2)(obj)).toBe(obj)

expect(set(1, 3)(ar)).toEqual([1, 3])
expect(set(1, 2)(ar)).toBe(ar)
})

test("unset", () => {
const ar = [1,2]
const obj = {
a: 1,
b: 2
}

expect(unset("b")(obj)).toEqual({ a: 1})
expect(unset("b")(obj)).not.toBe(obj)
expect(unset("c")(obj)).toBe(obj)

expect(unset(0)(ar)).toEqual([2])
expect(unset(0)(ar)).not.toBe(ar)
expect(unset(5)(ar)).toBe(ar)
})

test("push", () => {
const x = []
expect(push(3)(x)).not.toBe(x)
expect(push(3)([1])).toEqual([1, 3])
expect(push(3,4)([1])).toEqual([1, 3, 4])
expect(push()(x)).toBe(x)
})

test("splice", () => {
const x = [1,2]
expect(splice()(x)).toBe(x)
expect(splice(0)(x)).toEqual([])
expect(splice(0)(x)).not.toBe(x)
expect(splice(0,1)(x)).toEqual([2])
expect(splice(5,1)(x)).toBe(x)
expect(splice(5,1,3,4)(x)).toEqual([1,2,3,4])
expect(splice(0,0,3,4)(x)).toEqual([3,4,1,2])
expect(splice(0,1,3,4)(x)).toEqual([3,4,2])
})

test("shift", () => {
const x = []
const y = [1, 2]
expect(shift(x)).toBe(x)
expect(shift(y)).not.toBe(y)
expect(shift(y)).toEqual([2])
})

test("unshift", () => {
const y = [1, 2]
expect(unshift()(y)).toBe(y)
expect(unshift(1)(y)).not.toBe(y)
expect(unshift(3,4)(y)).toEqual([3,4,1,2])
})

test("pop", () => {
const x = []
const y = [1, 2]
expect(pop(x)).toBe(x)
expect(pop(y)).not.toBe(y)
expect(pop(y)).toEqual([1])
})

test("assign", () => {
const base = {
x: 1,
y: 2
}

expect(assign({})(base)).toBe(base)
expect(assign(null)(base)).toBe(base)
expect(assign({x: 1, y: 2})(base)).toBe(base)
expect(assign({x: 2, y: 2})(base)).not.toBe(base)
expect(assign({x: 2, y: 2})(base)).toEqual({ x: 2, y: 2 })
expect(assign({x: 2 })(base)).toEqual({ x: 2, y: 2 })
expect(assign({x: 2, z: 2 })(base)).toEqual({ x: 2, y: 2, z: 2 })
})

test("unset / removeBy / removeValue", () => {
const todo1 = {
id: "1",
title: "get coffee",
done: false
}
const todo2 = {
id: "2",
title: "get cookie",
done: false
}

const ar = [todo1, todo2]
const obj = { todo1, todo2 }
deepfreeze(ar)
deepfreeze(obj)

expect(unset(0)(ar)).toEqual([todo2])
expect(unset("todo1")(obj)).toEqual({todo2})

expect(removeBy(v => v.title.startsWith("get"))(ar)).toEqual([])
expect(removeBy(v => v.title.startsWith("get"))(obj)).toEqual({})

expect(removeBy(v => false)(ar)).toBe(ar)
expect(removeBy(v => false)(obj)).toBe(obj)

expect(removeBy("id", "2")(ar)).toEqual([todo1])
expect(removeBy("id", "2")(obj)).toEqual({todo1})

expect(removeBy("id", "3")(ar)).toBe(ar)
expect(removeBy("id", "3")(obj)).toBe(obj)

expect(removeValue({})(ar)).toBe(ar)
expect(removeValue({})(obj)).toBe(obj)

expect(removeValue(todo1)(ar)).toEqual([todo2])
expect(removeValue(todo1)(obj)).toEqual({ todo2 })
})
75 changes: 60 additions & 15 deletions pkgs/updaters/updaters.ts
Original file line number Diff line number Diff line change
@@ -1,3 +1,7 @@
export type KVMap<T> = {
[key: string]: T
}

export const toggle = (val: boolean) => !val

export const inc = (by: number) => (val: number) => val + by
Expand All @@ -6,9 +10,11 @@ export const inc1 = inc(1)
export const dec1 = inc(-1)
export const dec = (by: number) => inc(-by)

export const replace = <T>(newVal: T) => (_: T) => newVal
export const replace = <T>(newVal: T) => _ => newVal

export function set<T>(key: number, value: T): (o: T[]) => T[]
export function set<T, K extends keyof T>(key: K, value: T[K]): (o: T) => T
export function set<V, T extends KVMap<V>>(key: string, value: V): (o: T) => T
export function set(key, value) {
return o => {
if (o[key] === value) return o // no-op
Expand All @@ -17,41 +23,43 @@ export function set(key, value) {
res[key] = value
return res
}
return { ...o, key: value }
return { ...o, [key]: value }
}
}

export function remove<T, K extends keyof T>(key: K): (o: T) => T
export function remove(key) {
export function unset<T>(key: number): (o: T[]) => T[]
export function unset<T, K extends keyof T>(key: K): (o: T) => T
export function unset<V, T extends KVMap<V>>(key: string): (o: T) => T
export function unset(key) {
return o => {
if (Array.isArray(o)) return o.splice(key, 1)
if (Array.isArray(o)) return splice(key, 1)(o)
if (!(key in o)) return o // Noop delete
const res = {...o}
delete res[key]
return res
}
}

export function push<T>(value: T): (o: T[]) => T[]
export function push(value) {
export function push<T>(...values: T[]): (o: T[]) => T[]
export function push(...values) {
return o => {
if (!values.length) return o
const res = o.slice()
res.push(value)
res.push(...values)
return res
}
}

export function splice<T>(idx?: number, deleteCount?, toAdd?: T[]): (o: T[]) => T[]
export function splice(idx = 0, deleteCount = 0, toAdd = []) {
export function splice<T>(idx?: number | undefined, deleteCount?: number | undefined, ...toAdd: T[]): (o: T[]) => T[]
export function splice(idx?: any, deleteCount?: any, ...toAdd: any[]): any {
return o => {
if (!deleteCount && !toAdd.length) return o // no changes
if (!arguments.length || ((deleteCount === 0 || idx >= o.length) && !toAdd.length)) return o // no changes
const res = o.slice()
res.splice(idx, deleteCount, toAdd)
res.splice.apply(res, arguments)
return res
}
}


export function shift<T>(val: T[]): T[] {
if (!val.length) return val
const res = val.slice()
Expand All @@ -69,11 +77,48 @@ export function unshift<T>(...items: T[]): (o: T[]) => T[] {
}
}

export const assign = v => o => {
export function pop<T>(val: T[]): T[] {
if (!val.length) return val
const res = val.slice()
res.pop()
return res
}

export const assign = <T>(v:T) => (o: T) => {
if (!v) return o
let change = false
for (const key in v) if (o[key] !== v[key]) {
change = true
break
}
return change ? Object.assign({}, o, v) : o
return (change ? Object.assign({}, o, v) : o) as T
}

export function removeBy<T>(predicate: (val: T) => boolean): (o: T[]) => T[]
export function removeBy<V, T extends KVMap<V>>(predicate: (val: V) => boolean): (o: T) => T
export function removeBy<T, K extends keyof T>(key: K, value: T[K]): (o: T[]) => T[]
export function removeBy<V, T extends KVMap<V>, K extends keyof V>(key: K, value: V[K]): (o: T) => T
export function removeBy(arg1, arg2?) {
if (typeof arg1 !== "function")
return removeBy(v => v[arg1] === arg2)
return o => {
if (Array.isArray(o)) {
const res = o.filter(v => !arg1(v))
return res.length === o.length ? o: res
}
let match = false
const res = {}
Object.keys(o).forEach(k => {
if (!arg1(o[k]))
res[k] = o[k]
else
match = true
})
return match ? res : o
}
}

export function removeValue(value) {
return removeBy(v => v === value)
}

0 comments on commit 2c6e360

Please sign in to comment.