Skip to content

Commit

Permalink
Merge branch 'master' into no-more-this (WIP)
Browse files Browse the repository at this point in the history
  • Loading branch information
mweststrate committed Aug 17, 2017
2 parents abbb19e + 80d98e4 commit acf3aa3
Show file tree
Hide file tree
Showing 29 changed files with 540 additions and 57 deletions.
14 changes: 7 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
* [Volatile state](#volatile-state)
* [Dependency injection](#dependency-injection)
* [Types overview](#types-overview)
* [Lifecycle hooks](https://github.com/mobxjs/mobx-state-tree#lifecycle-hooks-for-typesmodel)
* [Lifecycle hooks](#lifecycle-hooks-for-typesmodel)
* [Api overview](#api-overview)
* [Tips](#tips)
* [FAQ](#FAQ)
Expand Down Expand Up @@ -362,9 +362,9 @@ Actions are replayable and are therefore constrained in several ways:
Useful methods:
- `onAction(model, listener)` listens to any action that is invoked on the model or any of its descendants. See `onAction` for more details.
- `addMiddleware(model, middleware)` listens to any action that is invoked on the model or any of its descendants. See `addMiddleware` for more details.
- `applyAction(model, action)` invokes an action on the model according to the given action description
- [`onAction`](API.md#onaction) listens to any action that is invoked on the model or any of its descendants.
- [`addMiddleware`](API.md#addmiddleware) listens to any action that is invoked on the model or any of its descendants.
- [`applyAction`](API.md#applyaction) invokes an action on the model according to the given action description
#### Asynchronous actions
Expand Down Expand Up @@ -403,7 +403,7 @@ For more details on creating middleware, see the [docs](docs/middleware.md)
#### Disabling protected mode
This may be desired if the default protection of mobx-state-tree doesn't fit your use case. For example, if you are not interested in replayable actions, or hate the effort of writing actions to modify any field; `unprotect(tree)` will disable the protected mode of a tree, allowing anyone to directly modify the tree.
This may be desired if the default protection of `mobx-state-tree` doesn't fit your use case. For example, if you are not interested in replayable actions, or hate the effort of writing actions to modify any field; `unprotect(tree)` will disable the protected mode of a tree, allowing anyone to directly modify the tree.
### Snapshots
Expand Down Expand Up @@ -743,7 +743,7 @@ Note, all hooks should be defined as actions.
# Api overview
See the [full API docs](https://github.com/mobxjs/mobx-state-tree/blob/master/API.md) for more details.
See the [full API docs](API.md) for more details.
| signature | |
| ---- | --- |
Expand Down Expand Up @@ -820,7 +820,7 @@ In the importing file
```javascript
import { LateStore } from "./circular-dep"

const Store = types.late(() => LateStore)
const Store = types.late(LateStore)
```
Thanks to function hoisting in combination with `types.late`, this lets you have circular dependencies between types, across files.
Expand Down
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@
"rollup": "rollup -c",
"build-tests": "tsc -p test/",
"test": "npm run build-tests && ava && npm run test-cyclic",
"speedtest": "npm run build-tests && node --expose-gc test-lib/test/perf/report.js",
"test-cyclic": "npm run build && node -e \"require('.')\"",
"watch": "tsc -p test && concurrently --kill-others --names 'build-tests,test-runner' 'tsc --watch -p test' --raw 'ava --watch'",
"_prepublish": "npm run build && npm run build-docs",
Expand Down Expand Up @@ -71,7 +72,7 @@
],
"ava": {
"files": [
"test-lib/test/**/*.js"
"test-lib/test/*.js"
],
"source": [
"test-lib/src/**/*.js"
Expand Down
2 changes: 1 addition & 1 deletion src/types/complex-types/array.ts
Original file line number Diff line number Diff line change
Expand Up @@ -193,7 +193,7 @@ export class ArrayType<S, T> extends ComplexType<S[], IObservableArray<T>> {

isValidSnapshot(value: any, context: IContext): IValidationResult {
if (!isArray(value)) {
return typeCheckFailure(context, value)
return typeCheckFailure(context, value, "Value is not an array")
}

return flattenTypeErrors(
Expand Down
2 changes: 1 addition & 1 deletion src/types/complex-types/map.ts
Original file line number Diff line number Diff line change
Expand Up @@ -246,7 +246,7 @@ export class MapType<S, T> extends ComplexType<{ [key: string]: S }, IExtendedOb

isValidSnapshot(value: any, context: IContext): IValidationResult {
if (!isPlainObject(value)) {
return typeCheckFailure(context, value)
return typeCheckFailure(context, value, "Value is not a plain object")
}

return flattenTypeErrors(
Expand Down
2 changes: 1 addition & 1 deletion src/types/complex-types/object.ts
Original file line number Diff line number Diff line change
Expand Up @@ -308,7 +308,7 @@ export class ObjectType<S, T> extends ComplexType<S, T> implements IModelType<S,
let snapshot = this.applySnapshotPreProcessor(value)

if (!isPlainObject(snapshot)) {
return typeCheckFailure(context, snapshot)
return typeCheckFailure(context, snapshot, "Value is not a plain object")
}

return flattenTypeErrors(
Expand Down
3 changes: 2 additions & 1 deletion src/types/primitives.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,7 +33,8 @@ export class CoreType<S, T> extends Type<S, T> {
if (isPrimitive(value) && this.checker(value)) {
return typeCheckSuccess()
}
return typeCheckFailure(context, value)
const typeName = this.name === "Date" ? "Date or a unix milliseconds timestamp" : this.name
return typeCheckFailure(context, value, `Value is not a ${typeName}`)
}
}

Expand Down
6 changes: 5 additions & 1 deletion src/types/utility-types/frozen.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,11 @@ export class Frozen<T> extends Type<T, T> {

isValidSnapshot(value: any, context: IContext): IValidationResult {
if (!isSerializable(value)) {
return typeCheckFailure(context, value)
return typeCheckFailure(
context,
value,
"Value is not serializable and cannot be frozen"
)
}
return typeCheckSuccess()
}
Expand Down
6 changes: 5 additions & 1 deletion src/types/utility-types/identifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -51,7 +51,11 @@ export class IdentifierType<T> extends Type<T, T> {
typeof value === "number"
)
return this.identifierType.validate(value, context)
return typeCheckFailure(context, value, "References should be a primitive value")
return typeCheckFailure(
context,
value,
"Value is not a valid identifier, which is a string or a number"
)
}
}

Expand Down
8 changes: 6 additions & 2 deletions src/types/utility-types/literal.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@ export class Literal<T> extends Type<T, T> {
readonly flags = TypeFlags.Literal

constructor(value: any) {
super("" + value)
super(JSON.stringify(value))
this.value = value
}

Expand All @@ -25,7 +25,11 @@ export class Literal<T> extends Type<T, T> {
if (isPrimitive(value) && value === this.value) {
return typeCheckSuccess()
}
return typeCheckFailure(context, value)
return typeCheckFailure(
context,
value,
`Value is not a literal ${JSON.stringify(this.value)}`
)
}
}

Expand Down
4 changes: 1 addition & 3 deletions src/types/utility-types/reference.ts
Original file line number Diff line number Diff line change
Expand Up @@ -95,9 +95,7 @@ export class ReferenceType<T> extends Type<string | number, T> {
: typeCheckFailure(
context,
value,
`Value '${prettyPrintValue(
value
)}' is not a valid reference. Expected a string or number.`
"Value is not a valid identifier, which is a string or a number"
)
}
}
Expand Down
18 changes: 12 additions & 6 deletions src/types/utility-types/refinement.ts
Original file line number Diff line number Diff line change
Expand Up @@ -33,14 +33,20 @@ export class Refinement<S, T> extends Type<S, T> {
}

isValidSnapshot(value: any, context: IContext): IValidationResult {
if (this.type.is(value)) {
const snapshot = isStateTreeNode(value) ? getStateTreeNode(value).snapshot : value
const subtypeErrors = this.type.validate(value, context)
if (subtypeErrors.length > 0) return subtypeErrors

if (this.predicate(snapshot)) {
return typeCheckSuccess()
}
const snapshot = isStateTreeNode(value) ? getStateTreeNode(value).snapshot : value

if (!this.predicate(snapshot)) {
return typeCheckFailure(
context,
value,
"Value does not respect the refinement predicate"
)
}
return typeCheckFailure(context, value)

return typeCheckSuccess()
}
}

Expand Down
12 changes: 5 additions & 7 deletions src/types/utility-types/union.ts
Original file line number Diff line number Diff line change
Expand Up @@ -77,14 +77,12 @@ export class Union extends Type<any, any> {
return typeCheckFailure(
context,
value,
"Multiple types are applicable and no dispatch method is defined for the union"
"Multiple types are applicable for the union (hint: provide a dispatch function)"
)
} else if (applicableTypes.length === 0) {
return typeCheckFailure(context, value, "No type is applicable for the union").concat(
flattenTypeErrors(errors)
)
} else if (applicableTypes.length < 1) {
return typeCheckFailure(
context,
value,
"No type is applicable and no dispatch method is defined for the union"
).concat(flattenTypeErrors(errors))
}

return typeCheckSuccess()
Expand Down
2 changes: 1 addition & 1 deletion test/enum.ts
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,6 @@ test("should support anonymous enums", t => {
// Note, any cast needed, compiler should correctly error otherwise
t.throws(
() => (l.color = "Blue" as any),
/Error while converting `"Blue"` to `Orange | Green | Red`/
/Error while converting `"Blue"` to `"Orange" | "Green" | "Red"`/
)
})
45 changes: 45 additions & 0 deletions test/fixture-data.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,45 @@
import { test } from "ava"
import { rando, createHeros, createMonsters, createTreasure } from "./fixtures/fixture-data"
import { Hero, Monster, Treasure } from "./fixtures/fixture-models"

test("createHeros", t => {
const data = createHeros(10)
t.is(data.length, 10)
const hero = Hero.create(data[0])
t.true(hero.descriptionLength > 1)
})

test("createMonsters", t => {
const data = createMonsters(10, 10, 10)
t.is(data.length, 10)
t.is(data[1].treasures.length, 10)
t.is(data[0].eatenHeroes.length, 10)
const monster = Monster.create(data[0])
t.true(monster.eatenHeroes && monster.eatenHeroes.length === 10)
t.true(monster.treasures.length === 10)
})

test("createTreasure", t => {
const data = createTreasure(10)
t.is(data.length, 10)
const treasure = Treasure.create(data[1])
t.true(treasure.gold > 0)
})

test("rando sorting", t => {
// i'm going straight to hell for this test... must get coverage to 100%.... no matter the cost.
let foundTrue = false
let foundFalse = false
let result
do {
result = rando()
if (result) {
foundTrue = true
} else {
foundFalse = true
}
} while (!foundTrue || !foundFalse)

t.true(foundTrue)
t.true(foundFalse)
})
51 changes: 51 additions & 0 deletions test/fixture-models.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
import { test } from "ava"
import { Hero, Monster, Treasure } from "./fixtures/fixture-models"
import { unprotect } from "../src"

const SAMPLE_HERO = {
id: 1,
name: "jimmy",
level: 1,
role: "cleric",
description: "hi"
}

test("Hero computed fields", t => {
const hero = Hero.create(SAMPLE_HERO)
t.is(hero.descriptionLength, 2)
})

test("Tresure", t => {
const treasure = Treasure.create({ gold: 1, trapped: true })
t.true(treasure.trapped)
t.is(treasure.gold, 1)
})

test("Monster computed fields", t => {
const monster = Monster.create({
id: "foo",
level: 1,
maxHp: 3,
hp: 1,
warning: "boo!",
createdAt: new Date(),
treasures: [{ gold: 2, trapped: true }, { gold: 3, trapped: true }],
eatenHeroes: [SAMPLE_HERO],
hasFangs: true,
hasClaws: true,
hasWings: true,
hasGrowl: true
})
t.true(monster.isAlive)
t.true(monster.isFlashingRed)
unprotect(monster)
t.is(monster.weight, 2)
monster.level = 0
monster.hasFangs = false
monster.hasWings = false
monster.eatenHeroes = null
t.is(monster.weight, 1)
monster.hp = 0
t.false(monster.isFlashingRed)
t.false(monster.isAlive)
})

0 comments on commit acf3aa3

Please sign in to comment.