minimongo
Version:
Client-side mongo database with server sync over http
975 lines (873 loc) • 26.9 kB
text/typescript
// TODO: This file was created by bulk-decaffeinate.
// Sanity-check the conversion and remove this comment.
import _ from "lodash"
import chai from "chai"
const { assert } = chai
function error(err: any) {
console.log(err)
return assert.fail(JSON.stringify(err))
}
// Runs queries on @col which must be a collection (with a:<string>, b:<integer>, c:<json>, geo:<geojson>, stringarr: <json array of strings>)
// When present:
// c.arrstr is an array of string values
// c.arrint is an array of integer values
// @reset(done) must truncate the collection
export default function () {
before(function (this: any) {
// Test a filter to return specified rows (in order)
return (this.testFilter = function (filter: any, ids: any, done: any) {
return this.col.find(filter, { sort: ["_id"] }).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ids)
done()
})
})
})
context("With sample rows", function () {
beforeEach(function (done: any) {
return this.reset(() => {
return this.col.upsert({ _id: "1", a: "Alice", b: 1, c: { d: 1, e: 2 } }, () => {
return this.col.upsert({ _id: "2", a: "Charlie", b: 2, c: { d: 2, e: 3 } }, () => {
return this.col.upsert({ _id: "3", a: "Bob", b: 3 }, () => done())
})
})
})
})
it("finds all rows", function (done: any) {
return this.col.find({}).fetch(function (results: any) {
assert.equal(results.length, 3)
done()
})
})
it("finds all rows (promise)", async function () {
const results = await this.col.find({}).fetch()
assert.equal(results.length, 3)
})
it("finds all rows with options", function (done: any) {
return this.col.find({}, {}).fetch(function (results: any) {
assert.equal(3, results.length)
done()
})
})
it("finds all rows with options (promise)", async function () {
const results = await this.col.find({}, {}).fetch()
assert.equal(3, results.length)
})
it("filters by id", function (done: any) {
return this.testFilter({ _id: "1" }, ["1"], done)
})
it("filters by string", function (done: any) {
return this.testFilter({ a: "Alice" }, ["1"], done)
})
it("filters by $in string", function (done: any) {
return this.testFilter({ a: { $in: ["Alice", "Charlie"] } }, ["1", "2"], done)
})
it("filters by number", function (done: any) {
return this.testFilter({ b: 2 }, ["2"], done)
})
it("filters by $in number", function (done: any) {
return this.testFilter({ b: { $in: [2, 3] } }, ["2", "3"], done)
})
it("filters by $regex", function (done: any) {
return this.testFilter({ a: { $regex: "li" } }, ["1", "2"], done)
})
it("filters by $regex case-sensitive", function (done: any) {
return this.testFilter({ a: { $regex: "A" } }, ["1"], done)
})
it("filters by $regex case-insensitive", function (done: any) {
return this.testFilter({ a: { $regex: "A", $options: "i" } }, ["1", "2"], done)
})
it("filters by $or", function (done: any) {
return this.testFilter({ $or: [{ b: 1 }, { b: 2 }] }, ["1", "2"], done)
})
it("filters by path", function (done: any) {
return this.testFilter({ "c.d": 2 }, ["2"], done)
})
it("filters by $ne", function (done: any) {
return this.testFilter({ b: { $ne: 2 } }, ["1", "3"], done)
})
it("filters by $gt", function (done: any) {
return this.testFilter({ b: { $gt: 1 } }, ["2", "3"], done)
})
it("filters by $lt", function (done: any) {
return this.testFilter({ b: { $lt: 3 } }, ["1", "2"], done)
})
it("filters by $gte", function (done: any) {
return this.testFilter({ b: { $gte: 2 } }, ["2", "3"], done)
})
it("filters by $lte", function (done: any) {
return this.testFilter({ b: { $lte: 2 } }, ["1", "2"], done)
})
it("filters by $not", function (done: any) {
return this.testFilter({ b: { $not: { $lt: 3 } } }, ["3"], done)
})
it("filters by $or", function (done: any) {
return this.testFilter({ $or: [{ b: 3 }, { b: 1 }] }, ["1", "3"], done)
})
it("filters by $exists: true", function (done: any) {
return this.testFilter({ c: { $exists: true } }, ["1", "2"], done)
})
it("filters by $exists: false", function (done: any) {
return this.testFilter({ c: { $exists: false } }, ["3"], done)
})
it("includes fields", function (done: any) {
return this.col.find({ _id: "1" }, { fields: { a: 1 } }).fetch(function (results: any) {
assert.deepEqual(results[0], { _id: "1", a: "Alice" })
done()
})
})
it("includes subfields", function (done: any) {
return this.col.find({ _id: "1" }, { fields: { "c.d": 1 } }).fetch(function (results: any) {
assert.deepEqual(results[0], { _id: "1", c: { d: 1 } })
done()
})
})
it("ignores non-existent subfields", function (done: any) {
return this.col.find({ _id: "1" }, { fields: { "x.y": 1 } }).fetch(function (results: any) {
assert.deepEqual(results[0], { _id: "1" })
done()
})
})
it("excludes fields", function (done: any) {
return this.col.find({ _id: "1" }, { fields: { a: 0 } }).fetch(function (results: any) {
assert.isUndefined(results[0].a)
assert.equal(results[0].b, 1)
done()
})
})
it("excludes subfields", function (done: any) {
return this.col.find({ _id: "1" }, { fields: { "c.d": 0 } }).fetch(function (results: any) {
assert.deepEqual(results[0].c, { e: 2 })
done()
})
})
it("finds one row", function (done: any) {
return this.col.findOne({ _id: "2" }, function (result: any) {
assert.equal("Charlie", result.a)
done()
})
})
it("finds one row (promise)", async function () {
const result = await this.col.findOne({ _id: "2" })
assert.equal("Charlie", result.a)
})
it("removes item", function (done: any) {
return this.col.remove(
"2",
() => {
return this.col.find({}).fetch(function (results: any) {
let needle, needle1
let result
assert.equal(2, results.length)
assert(
((needle = "1"),
(() => {
const result1 = []
for (result of results) {
result1.push(result._id)
}
return result1
})().includes(needle))
)
assert(
((needle1 = "2"),
!(() => {
const result2 = []
for (result of results) {
result2.push(result._id)
}
return result2
})().includes(needle1))
)
done()
}, error)
},
error
)
})
it("removes item (promise)", async function () {
await this.col.remove("2")
const results = await this.col.find({}).fetch()
assert.equal(2, results.length)
let needle, needle1
let result
assert(
((needle = "1"),
(() => {
const result1 = []
for (result of results) {
result1.push(result._id)
}
return result1
})().includes(needle))
)
assert(
((needle1 = "2"),
!(() => {
const result2 = []
for (result of results) {
result2.push(result._id)
}
return result2
})().includes(needle1))
)
})
it("removes non-existent item", function (done: any) {
return this.col.remove("999", () => {
return this.col.find({}).fetch(function (results: any) {
assert.equal(3, results.length)
done()
})
})
})
it("sorts ascending", function (done: any) {
return this.col.find({}, { sort: ["a"] }).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "3", "2"])
done()
})
})
it("sorts descending", function (done: any) {
return this.col.find({}, { sort: [["a", "desc"]] }).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["2", "3", "1"])
done()
})
})
it("limits", function (done: any) {
return this.col.find({}, { sort: ["a"], limit: 2 }).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "3"])
done()
})
})
it("skips", function (done: any) {
return this.col.find({}, { sort: ["a"], skip: 2 }).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["2"])
done()
})
})
// MemoryDb is much faster if we relax this constraint
it("fetches independent copies", function (done: any) {
return this.col.findOne({ _id: "2" }, (result1: any) => {
return this.col.findOne({ _id: "2" }, function (result2: any) {
assert(result1 !== result2)
done()
})
})
})
// MemoryDb is much faster if we relax this constraint
it("upsert keeps independent copies", function (done: any) {
const doc: any = { _id: "2" }
return this.col.upsert(doc, (item: any) => {
doc.a = "xyz"
item.a = "xyz"
return this.col.findOne({ _id: "2" }, function (doc2: any) {
assert(doc !== doc2)
assert(doc2.a !== "xyz")
done()
})
})
})
// MemoryDb is much faster if we relax this constraint
it("upsert keeps independent copies (promise)", async function () {
const doc: any = { _id: "2" }
const item = await this.col.upsert(doc)
doc.a = "xyz"
item.a = "xyz"
const doc2 = await this.col.findOne({ _id: "2" })
assert(doc !== doc2)
assert(doc2.a !== "xyz")
})
it("upsert returns doc", function (done: any) {
const doc: any = { _id: "2" }
this.col.upsert(doc, (item: any) => {
assert.equal(item._id, "2")
done()
}, () => {
assert.fail()
})
})
it("upsert returns doc (promise)", async function () {
const doc: any = { _id: "2" }
const item = await this.col.upsert(doc)
assert.equal(item._id, "2")
})
it("adds _id to rows", function (done: any) {
return this.col.upsert({ a: "1" }, function (item: any) {
assert.property(item, "_id")
assert.lengthOf(item._id, 32)
done()
})
})
it("returns array if called with array", function (done: any) {
return this.col.upsert([{ a: "1" }], function (items: any) {
assert.equal(items[0].a, "1")
done()
})
})
it("updates by id", function (done: any) {
return this.col.upsert({ _id: "1", a: "1" }, (item: any) => {
return this.col.upsert({ _id: "1", a: "2", b: 1 }, (item: any) => {
assert.equal(item.a, "2")
return this.col.find({ _id: "1" }).fetch(function (results: any) {
assert.equal(1, results.length, "Should be only one document")
done()
})
})
})
})
it("call upsert with upserted row", function (done: any) {
return this.col.upsert({ _id: "1", a: "1" }, function (item: any) {
assert.equal(item._id, "1")
assert.equal(item.a, "1")
done()
})
})
})
it("upserts multiple rows", function (done: any) {
this.timeout(10000)
return this.reset(() => {
const docs = []
for (let i = 0; i < 100; i++) {
docs.push({ b: i })
}
return this.col.upsert(
docs,
() => {
return this.col.find({}).fetch(function (results: any) {
assert.equal(results.length, 100)
done()
}, error)
},
error
)
})
})
it("upserts multiple rows (promise)", function (done: any) {
this.timeout(10000)
return this.reset(async () => {
const docs = []
for (let i = 0; i < 100; i++) {
docs.push({ b: i })
}
await this.col.upsert(docs)
const results = await this.col.find({}).fetch()
assert.equal(results.length, 100)
done()
})
})
context("With sample with capitalization", function () {
beforeEach(function (done: any) {
return this.reset(() => {
return this.col.upsert({ _id: "1", a: "Alice", b: 1, c: { d: 1, e: 2 } }, () => {
return this.col.upsert({ _id: "2", a: "AZ", b: 2, c: { d: 2, e: 3 } }, () => done())
})
})
})
it("finds sorts in Javascript order", function (done: any) {
return this.col.find({}, { sort: ["a"] }).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["2", "1"])
done()
})
})
})
context("With integer array in json rows", function () {
beforeEach(function (done: any) {
return this.reset(() => {
return this.col.upsert({ _id: "1", c: { arrint: [1, 2] } }, () => {
return this.col.upsert({ _id: "2", c: { arrint: [2, 3] } }, () => {
return this.col.upsert({ _id: "3", c: { arrint: [1, 3] } }, () => done())
})
})
})
})
it("filters by $in", function (done: any) {
return this.testFilter({ "c.arrint": { $in: [3] } }, ["2", "3"], done)
})
it("filters by list $in with multiple", function (done: any) {
return this.testFilter({ "c.arrint": { $in: [1, 3] } }, ["1", "2", "3"], done)
})
})
context("With object array rows", function () {
beforeEach(function (done: any) {
return this.reset(() => {
return this.col.upsert(
{
_id: "1",
c: [
{ x: 1, y: 1 },
{ x: 1, y: 2 }
]
},
() => {
return this.col.upsert({ _id: "2", c: [{ x: 2, y: 1 }] }, () => {
return this.col.upsert({ _id: "3", c: [{ x: 2, y: 2 }] }, () => done())
})
}
)
})
})
it("filters by $elemMatch", function (done: any) {
return this.testFilter({ c: { $elemMatch: { y: 1 } } }, ["1", "2"], () => {
return this.testFilter({ c: { $elemMatch: { x: 1 } } }, ["1"], done)
})
})
})
context("With array rows with inner string arrays", function () {
beforeEach(function (done: any) {
return this.reset(() => {
return this.col.upsert({ _id: "1", c: [{ arrstr: ["a", "b"] }, { arrstr: ["b", "c"] }] }, () => {
return this.col.upsert({ _id: "2", c: [{ arrstr: ["b"] }] }, () => {
return this.col.upsert({ _id: "3", c: [{ arrstr: ["c", "d"] }, { arrstr: ["e", "f"] }] }, () => done())
})
})
})
})
it("filters by $elemMatch", function (done: any) {
return this.testFilter({ c: { $elemMatch: { arrstr: { $in: ["b"] } } } }, ["1", "2"], () => {
return this.testFilter({ c: { $elemMatch: { arrstr: { $in: ["d", "e"] } } } }, ["3"], done)
})
})
})
context("With text array rows", function () {
beforeEach(function (done: any) {
return this.reset(() => {
return this.col.upsert(
{ _id: "1", textarr: ["a", "b"] },
() => {
return this.col.upsert(
{ _id: "2", textarr: ["b", "c"] },
() => {
return this.col.upsert({ _id: "3", textarr: ["c", "d"] }, () => done(), error)
},
error
)
},
error
)
})
})
it("filters by $in", function (done: any) {
return this.testFilter({ textarr: { $in: ["b"] } }, ["1", "2"], done)
})
it("filters by direct reference", function (done: any) {
return this.testFilter({ textarr: "b" }, ["1", "2"], done)
})
it("filters by both item and complete array", function (done: any) {
return this.testFilter({ textarr: { $in: ["a", ["b", "c"]] } }, ["1", "2"], done)
})
})
const geopoint = (lng: number, lat: number) => ({
type: "Point",
coordinates: [lng, lat]
})
const geoline = (lng1: number, lat1: number, lng2: number, lat2: number) => ({
type: "LineString",
coordinates: [[lng1, lat1], [lng2, lat2]]
})
context("With geolocated rows", function () {
beforeEach(function (done: any) {
this.reset(() => {
this.col.upsert({ _id: "1", geo: geopoint(90, 45) }, () => {
this.col.upsert({ _id: "2", geo: geopoint(90, 46) }, () => {
this.col.upsert({ _id: "3", geo: geopoint(91, 45) }, () => {
this.col.upsert({ _id: "4", geo: geopoint(91, 46) }, () => done())
})
})
})
})
})
it("finds points near", function (done: any) {
const selector = {
geo: {
$near: {
$geometry: geopoint(90, 45)
}
}
}
this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "3", "2", "4"])
done()
})
})
it("finds points near maxDistance", function (done: any) {
const selector = {
geo: {
$near: {
$geometry: geopoint(90, 45),
$maxDistance: 111180
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "3"])
done()
})
})
it("finds points near maxDistance just above", function (done: any) {
const selector = {
geo: {
$near: {
$geometry: geopoint(90, 45),
$maxDistance: 111410
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "3", "2"])
done()
})
})
it("finds points within simple box", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: {
type: "Polygon",
coordinates: [
[
[89.5, 45.5],
[89.5, 46.5],
[90.5, 46.5],
[90.5, 45.5],
[89.5, 45.5]
]
]
}
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["2"])
done()
})
})
it("finds points within big box", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: {
type: "Polygon",
coordinates: [
[
[0, -89],
[0, 89],
[179, 89],
[179, -89],
[0, -89]
]
]
}
}
}
}
return this.col.find(selector, { sort: ["_id"] }).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "2", "3", "4"])
done()
})
})
it("handles undefined", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: {
type: "Polygon",
coordinates: [
[
[89.5, 45.5],
[89.5, 46.5],
[90.5, 46.5],
[90.5, 45.5],
[89.5, 45.5]
]
]
}
}
}
}
return this.col.upsert({ _id: 5 }, () => {
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["2"])
done()
})
})
})
})
context("With rows that are lines and points", function () {
beforeEach(function (done: any) {
this.reset(() => {
this.col.upsert({ _id: "1", geo: geopoint(1, 0) }, () => {
this.col.upsert({ _id: "2", geo: geoline(0, 1, 0, -1) }, () => {
this.col.upsert({ _id: "3", geo: geoline(10, 1, 10, -1) }, () => done())
})
})
})
})
it("finds near point", function (done: any) {
const selector = {
geo: {
$near: {
$geometry: geopoint(1, 0)
}
}
}
this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "2", "3"])
done()
})
})
it("finds points near maxDistance to exclude #3", function (done: any) {
const selector = {
geo: {
$near: {
$geometry: geopoint(1, 0),
$maxDistance: 200000
}
}
}
this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "2"])
done()
})
})
})
context("With polygon rows", function () {
const polygon = (coords: any) => ({
type: "Polygon",
coordinates: coords
})
beforeEach(function (done: any) {
this.reset(() => {
this.col.upsert(
{
_id: "1",
geo: polygon([
[
[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0, 0]
]
])
},
() => {
return this.col.upsert(
{
_id: "2",
geo: polygon([
[
[10, 10],
[11, 10],
[11, 11],
[10, 11],
[10, 10]
]
])
},
() => {
done()
}
)
}
)
})
})
it("finds polygons that intersect simple box", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: polygon([
[
[0, 0],
[2, 0],
[2, 2],
[0, 2],
[0, 0]
]
])
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1"])
done()
})
})
it("finds polygons that intersect large box", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: polygon([
[
[0, 0],
[12, 0],
[12, 12],
[0, 12],
[0, 0]
]
])
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "2"])
done()
})
})
})
context("With multipolygon rows", function () {
const polygon = (coords: any) => ({
type: "Polygon",
coordinates: coords
})
const multipolygon = (coords: any) => ({
type: "MultiPolygon",
coordinates: coords
})
beforeEach(function (done: any) {
return this.col.upsert(
{
_id: "1",
geo: multipolygon([
[
[
[0, 0],
[1, 0],
[1, 1],
[0, 1],
[0, 0]
]
]
])
},
() => {
return this.col.upsert(
{
_id: "2",
geo: multipolygon([
[
[
[10, 10],
[11, 10],
[11, 11],
[10, 11],
[10, 10]
]
]
])
},
() => {
done()
}
)
}
)
})
it("finds polygons that intersect simple box", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: polygon([
[
[0, 0],
[2, 0],
[2, 2],
[0, 2],
[0, 0]
]
])
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1"])
done()
})
})
it("finds polygons that intersect large box", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: polygon([
[
[0, 0],
[12, 0],
[12, 12],
[0, 12],
[0, 0]
]
])
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1", "2"])
done()
})
})
})
return context("With multilinestring rows", function () {
const polygon = (coords: any) => ({
type: "Polygon",
coordinates: coords
})
beforeEach(function (done: any) {
const linestring = {
type: "MultiLineString",
coordinates: [
[
[0, 0],
[0, 1]
],
[
[0, 0],
[1, 0]
]
]
}
return this.col.upsert({ _id: "1", geo: linestring }, () => {
done()
})
})
it("finds that that intersect simple box", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: polygon([
[
[0, 0],
[2, 0],
[2, 2],
[0, 2],
[0, 0]
]
])
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), ["1"])
done()
})
})
it("finds that that doesn't intersect simple box", function (done: any) {
const selector = {
geo: {
$geoIntersects: {
$geometry: polygon([
[
[2, 2],
[3, 2],
[3, 3],
[2, 3],
[2, 2]
]
])
}
}
}
return this.col.find(selector).fetch(function (results: any) {
assert.deepEqual(_.map(results, "_id"), [])
done()
})
})
})
}