kopia lustrzana https://github.com/Tldraw/Tldraw
824 wiersze
24 KiB
TypeScript
824 wiersze
24 KiB
TypeScript
import { atom, RESET_VALUE } from 'signia'
|
|
import { BaseRecord, ID } from '../BaseRecord'
|
|
import { createRecordType } from '../RecordType'
|
|
import { Store } from '../Store'
|
|
import { StoreSchema } from '../StoreSchema'
|
|
|
|
interface Author extends BaseRecord<'author'> {
|
|
name: string
|
|
age: number
|
|
}
|
|
const Author = createRecordType<Author>('author', {
|
|
validator: {
|
|
validate(value) {
|
|
const author = value as Author
|
|
if (author.typeName !== 'author') throw Error()
|
|
if (!author.id.startsWith('author:')) throw Error()
|
|
if (!Number.isFinite(author.age)) throw Error()
|
|
if (author.age < 0) throw Error()
|
|
return author
|
|
},
|
|
},
|
|
scope: 'document',
|
|
}).withDefaultProperties(() => ({ age: 23 }))
|
|
|
|
interface Book extends BaseRecord<'book'> {
|
|
title: string
|
|
authorId: ID<Author>
|
|
}
|
|
const Book = createRecordType<Book>('book', {
|
|
validator: {
|
|
validate(value) {
|
|
const book = value as Book
|
|
if (!book.id.startsWith('book:')) throw Error()
|
|
if (book.typeName !== 'book') throw Error()
|
|
if (typeof book.title !== 'string') throw Error()
|
|
if (!book.authorId.startsWith('author')) throw Error()
|
|
return book
|
|
},
|
|
},
|
|
scope: 'document',
|
|
})
|
|
const authors = {
|
|
tolkein: Author.create({ name: 'J.R.R. Tolkein' }),
|
|
bradbury: Author.create({ name: 'Ray Bradbury' }),
|
|
davidMitchellSerious: Author.create({ name: 'David Mitchell' }),
|
|
davidMitchellFunny: Author.create({ name: 'David Mitchell' }),
|
|
}
|
|
|
|
const books = {
|
|
cloudAtlas: Book.create({ title: 'Cloud Atlas', authorId: authors.davidMitchellSerious.id }),
|
|
myLifeInComedy: Book.create({
|
|
title: 'My Life in Comedy',
|
|
authorId: authors.davidMitchellFunny.id,
|
|
}),
|
|
lotr: Book.create({ title: 'Lord of the Rings', authorId: authors.tolkein.id }),
|
|
farenheit: Book.create({ title: 'Farenheit 451', authorId: authors.bradbury.id }),
|
|
}
|
|
|
|
let store: Store<Author | Book>
|
|
|
|
beforeEach(() => {
|
|
store = new Store({
|
|
props: {},
|
|
schema: StoreSchema.create<Author | Book>(
|
|
{
|
|
author: Author,
|
|
book: Book,
|
|
},
|
|
{
|
|
snapshotMigrations: {
|
|
currentVersion: 0,
|
|
firstVersion: 0,
|
|
migrators: {},
|
|
},
|
|
}
|
|
),
|
|
})
|
|
store.put([
|
|
authors.tolkein,
|
|
authors.bradbury,
|
|
books.lotr,
|
|
books.farenheit,
|
|
authors.davidMitchellFunny,
|
|
authors.davidMitchellSerious,
|
|
books.cloudAtlas,
|
|
books.myLifeInComedy,
|
|
])
|
|
})
|
|
|
|
describe('indexes', () => {
|
|
it('are cached', () => {
|
|
const authorNameIndex1 = store.query.index('author', 'name')
|
|
const authorNameIndex2 = store.query.index('author', 'name')
|
|
expect(authorNameIndex1).toBe(authorNameIndex2)
|
|
})
|
|
it('can be made on any property', () => {
|
|
const authorNameIndex = store.query.index('author', 'name')
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
|
|
expect(authorNameIndex.value.get('David Mitchell')).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
const bookTitleIndex = store.query.index('book', 'title')
|
|
expect(bookTitleIndex.value.get('Cloud Atlas')).toEqual(new Set([books.cloudAtlas.id]))
|
|
expect(bookTitleIndex.value.get('Lord of the Rings')).toEqual(new Set([books.lotr.id]))
|
|
})
|
|
|
|
it('can have things added when records are added', () => {
|
|
const authorNameIndex = store.query.index('author', 'name')
|
|
// deref to make it compute once
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
const newAuthor = Author.create({ name: 'New Author' })
|
|
|
|
store.put([newAuthor])
|
|
|
|
expect(authorNameIndex.value.get('New Author')).toEqual(new Set([newAuthor.id]))
|
|
|
|
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
if (diff === RESET_VALUE) throw new Error('should not be reset')
|
|
expect(diff).toHaveLength(1)
|
|
expect(diff[0].get('New Author')).toEqual({
|
|
added: new Set([newAuthor.id]),
|
|
})
|
|
expect(diff[0].size).toBe(1)
|
|
|
|
const moreNewAuthors = [
|
|
Author.create({ name: 'New Author' }),
|
|
Author.create({ name: 'New Author' }),
|
|
Author.create({ name: 'New Author' }),
|
|
]
|
|
|
|
lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
store.put(moreNewAuthors)
|
|
|
|
expect(authorNameIndex.value.get('New Author')).toEqual(
|
|
new Set([newAuthor.id, ...moreNewAuthors.map((a) => a.id)])
|
|
)
|
|
|
|
const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
|
|
if (diff2 === RESET_VALUE) throw new Error('should not be reset')
|
|
|
|
expect(diff2).toHaveLength(1)
|
|
expect(diff2[0].get('New Author')).toEqual({ added: new Set(moreNewAuthors.map((a) => a.id)) })
|
|
})
|
|
|
|
it('can have things added when records are updated', () => {
|
|
const authorNameIndex = store.query.index('author', 'name')
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
|
|
store.put([{ ...authors.bradbury, name: 'J.R.R. Tolkein' }])
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(
|
|
new Set([authors.tolkein.id, authors.bradbury.id])
|
|
)
|
|
|
|
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
if (diff === RESET_VALUE) throw new Error('should not be reset')
|
|
expect(diff).toHaveLength(1)
|
|
expect(diff[0].size).toBe(2)
|
|
expect(diff[0].get('J.R.R. Tolkein')).toEqual({
|
|
added: new Set([authors.bradbury.id]),
|
|
})
|
|
|
|
expect(diff[0].get('Ray Bradbury')).toEqual({
|
|
removed: new Set([authors.bradbury.id]),
|
|
})
|
|
|
|
lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
store.put([
|
|
{ ...authors.davidMitchellFunny, name: 'J.R.R. Tolkein' },
|
|
{ ...authors.davidMitchellSerious, name: 'J.R.R. Tolkein' },
|
|
])
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(
|
|
new Set([
|
|
authors.tolkein.id,
|
|
authors.bradbury.id,
|
|
authors.davidMitchellFunny.id,
|
|
authors.davidMitchellSerious.id,
|
|
])
|
|
)
|
|
|
|
const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
|
|
if (diff2 === RESET_VALUE) throw new Error('should not be reset')
|
|
|
|
expect(diff2).toHaveLength(1)
|
|
expect(diff2[0].size).toBe(2)
|
|
|
|
expect(diff2[0].get('J.R.R. Tolkein')).toEqual({
|
|
added: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
|
|
})
|
|
|
|
expect(diff2[0].get('David Mitchell')).toEqual({
|
|
removed: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
|
|
})
|
|
})
|
|
|
|
it('can have things removed when records are removed', () => {
|
|
const authorNameIndex = store.query.index('author', 'name')
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
|
|
store.remove([authors.tolkein.id])
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(undefined)
|
|
|
|
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
if (diff === RESET_VALUE) throw new Error('should not be reset')
|
|
expect(diff).toHaveLength(1)
|
|
expect(diff[0].size).toBe(1)
|
|
expect(diff[0].get('J.R.R. Tolkein')).toEqual({
|
|
removed: new Set([authors.tolkein.id]),
|
|
})
|
|
|
|
lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
store.remove([
|
|
authors.bradbury.id,
|
|
authors.davidMitchellFunny.id,
|
|
authors.davidMitchellSerious.id,
|
|
])
|
|
|
|
expect(authorNameIndex.value.get('Ray Bradbury')).toEqual(undefined)
|
|
expect(authorNameIndex.value.get('David Mitchell')).toEqual(undefined)
|
|
|
|
const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
|
|
if (diff2 === RESET_VALUE) throw new Error('should not be reset')
|
|
|
|
expect(diff2).toHaveLength(1)
|
|
expect(diff2[0].size).toBe(2)
|
|
|
|
expect(diff2[0].get('Ray Bradbury')).toEqual({
|
|
removed: new Set([authors.bradbury.id]),
|
|
})
|
|
|
|
expect(diff2[0].get('David Mitchell')).toEqual({
|
|
removed: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
|
|
})
|
|
})
|
|
|
|
it('can have things removed when records are updated', () => {
|
|
const authorNameIndex = store.query.index('author', 'name')
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
|
|
store.put([{ ...authors.tolkein, name: 'Ray Bradbury' }])
|
|
|
|
expect(authorNameIndex.value.get('Ray Bradbury')).toEqual(
|
|
new Set([authors.tolkein.id, authors.bradbury.id])
|
|
)
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(undefined)
|
|
|
|
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
if (diff === RESET_VALUE) throw new Error('should not be reset')
|
|
expect(diff).toHaveLength(1)
|
|
expect(diff[0].size).toBe(2)
|
|
expect(diff[0].get('J.R.R. Tolkein')).toEqual({
|
|
removed: new Set([authors.tolkein.id]),
|
|
})
|
|
|
|
expect(diff[0].get('Ray Bradbury')).toEqual({
|
|
added: new Set([authors.tolkein.id]),
|
|
})
|
|
|
|
lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
store.put([
|
|
{ ...authors.davidMitchellFunny, name: 'Ray Bradbury' },
|
|
{ ...authors.davidMitchellSerious, name: 'Ray Bradbury' },
|
|
])
|
|
|
|
expect(authorNameIndex.value.get('Ray Bradbury')).toEqual(
|
|
new Set([
|
|
authors.tolkein.id,
|
|
authors.bradbury.id,
|
|
authors.davidMitchellFunny.id,
|
|
authors.davidMitchellSerious.id,
|
|
])
|
|
)
|
|
expect(authorNameIndex.value.get('David Mitchell')).toEqual(undefined)
|
|
|
|
const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
|
|
if (diff2 === RESET_VALUE) throw new Error('should not be reset')
|
|
|
|
expect(diff2).toHaveLength(1)
|
|
expect(diff2[0].size).toBe(2)
|
|
|
|
expect(diff2[0].get('David Mitchell')).toEqual({
|
|
removed: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
|
|
})
|
|
|
|
expect(diff2[0].get('Ray Bradbury')).toEqual({
|
|
added: new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id]),
|
|
})
|
|
})
|
|
|
|
it('handles things being removed and added for the same value at the same time', () => {
|
|
const authorNameIndex = store.query.index('author', 'name')
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
let lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
|
|
// do remove first
|
|
store.remove([authors.tolkein.id])
|
|
const newAuthor = Author.create({ name: 'J.R.R. Tolkein' })
|
|
store.put([newAuthor])
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([newAuthor.id]))
|
|
|
|
const diff = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
|
|
if (diff === RESET_VALUE) throw new Error('should not be reset')
|
|
|
|
expect(diff).toHaveLength(1)
|
|
expect(diff[0].size).toBe(1)
|
|
|
|
expect(diff[0].get('J.R.R. Tolkein')).toEqual({
|
|
removed: new Set([authors.tolkein.id]),
|
|
added: new Set([newAuthor.id]),
|
|
})
|
|
|
|
lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
// do updates
|
|
store.put([{ ...authors.davidMitchellFunny, name: 'Ray Bradbury' }])
|
|
store.put([{ ...authors.bradbury, name: 'David Mitchell' }])
|
|
|
|
expect(authorNameIndex.value.get('Ray Bradbury')).toEqual(
|
|
new Set([authors.davidMitchellFunny.id])
|
|
)
|
|
expect(authorNameIndex.value.get('David Mitchell')).toEqual(
|
|
new Set([authors.bradbury.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
const diff2 = authorNameIndex.getDiffSince(lastChangedEpoch)
|
|
|
|
if (diff2 === RESET_VALUE) throw new Error('should not be reset')
|
|
|
|
expect(diff2).toHaveLength(1)
|
|
expect(diff2[0].size).toBe(2)
|
|
|
|
expect(diff2[0].get('Ray Bradbury')).toEqual({
|
|
added: new Set([authors.davidMitchellFunny.id]),
|
|
removed: new Set([authors.bradbury.id]),
|
|
})
|
|
|
|
expect(diff2[0].get('David Mitchell')).toEqual({
|
|
added: new Set([authors.bradbury.id]),
|
|
removed: new Set([authors.davidMitchellFunny.id]),
|
|
})
|
|
})
|
|
|
|
it('has the same value if nothing changed', () => {
|
|
const authorNameIndex = store.query.index('author', 'name')
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
const lastChangedEpoch = authorNameIndex.lastChangedEpoch
|
|
|
|
store.put([{ ...authors.tolkein, age: 23 }])
|
|
|
|
expect(authorNameIndex.value.get('J.R.R. Tolkein')).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
expect(lastChangedEpoch).toBe(authorNameIndex.lastChangedEpoch)
|
|
})
|
|
})
|
|
|
|
describe('queries for ids', () => {
|
|
it('can query for all values of a given type', () => {
|
|
const bookQuery = store.query.ids('book')
|
|
|
|
expect(bookQuery.value).toEqual(
|
|
new Set([books.cloudAtlas.id, books.farenheit.id, books.lotr.id, books.myLifeInComedy.id])
|
|
)
|
|
|
|
const authorQuery = store.query.ids('author')
|
|
|
|
expect(authorQuery.value).toEqual(
|
|
new Set([
|
|
authors.bradbury.id,
|
|
authors.davidMitchellFunny.id,
|
|
authors.davidMitchellSerious.id,
|
|
authors.tolkein.id,
|
|
])
|
|
)
|
|
|
|
const newAuthor = Author.create({ name: 'J.R.R. Tolkein' })
|
|
const newBook = Book.create({ title: 'The Hobbit', authorId: newAuthor.id })
|
|
store.put([newAuthor, newBook])
|
|
|
|
expect(bookQuery.value).toEqual(
|
|
new Set([
|
|
books.cloudAtlas.id,
|
|
books.farenheit.id,
|
|
books.lotr.id,
|
|
books.myLifeInComedy.id,
|
|
newBook.id,
|
|
])
|
|
)
|
|
|
|
expect(authorQuery.value).toEqual(
|
|
new Set([
|
|
authors.bradbury.id,
|
|
authors.davidMitchellFunny.id,
|
|
authors.davidMitchellSerious.id,
|
|
authors.tolkein.id,
|
|
newAuthor.id,
|
|
])
|
|
)
|
|
})
|
|
|
|
it('can query for a single value', () => {
|
|
const jrr = store.query.ids('author', () => ({
|
|
name: { eq: 'J.R.R. Tolkein' },
|
|
}))
|
|
|
|
expect(jrr.value).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
})
|
|
|
|
it('can query for multiple values', () => {
|
|
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
|
|
|
|
const mitchell30 = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
age: { eq: 30 },
|
|
}))
|
|
|
|
expect(mitchell30.value).toEqual(new Set([authors.davidMitchellFunny.id]))
|
|
})
|
|
|
|
it('can use a reactive query', () => {
|
|
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
|
|
|
|
const currentAuthor = atom('currentAuthor', 'David Mitchell')
|
|
const currentAge = atom('currentAge', 30)
|
|
|
|
const mitchell30 = store.query.ids('author', () => ({
|
|
name: { eq: currentAuthor.value },
|
|
age: { eq: currentAge.value },
|
|
}))
|
|
|
|
expect(mitchell30.value).toEqual(new Set([authors.davidMitchellFunny.id]))
|
|
|
|
let lastChangedEpoch = mitchell30.lastChangedEpoch
|
|
currentAge.set(23)
|
|
|
|
expect(mitchell30.value).toEqual(new Set([authors.davidMitchellSerious.id]))
|
|
|
|
const diff = mitchell30.getDiffSince(lastChangedEpoch)
|
|
|
|
if (diff === RESET_VALUE) throw new Error('should not be reset')
|
|
|
|
expect(diff).toHaveLength(1)
|
|
expect(diff[0]).toEqual({
|
|
added: new Set([authors.davidMitchellSerious.id]),
|
|
removed: new Set([authors.davidMitchellFunny.id]),
|
|
})
|
|
|
|
currentAuthor.set('J.R.R. Tolkein')
|
|
|
|
lastChangedEpoch = mitchell30.lastChangedEpoch
|
|
|
|
expect(mitchell30.value).toEqual(new Set([authors.tolkein.id]))
|
|
|
|
const diff2 = mitchell30.getDiffSince(lastChangedEpoch)
|
|
|
|
if (diff2 === RESET_VALUE) throw new Error('should not be reset')
|
|
|
|
expect(diff2).toHaveLength(1)
|
|
|
|
expect(diff2[0]).toEqual({
|
|
added: new Set([authors.tolkein.id]),
|
|
removed: new Set([authors.davidMitchellSerious.id]),
|
|
})
|
|
})
|
|
|
|
it('supports not-equals matches', () => {
|
|
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { neq: 'David Mitchell' },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(new Set([authors.tolkein.id, authors.bradbury.id]))
|
|
|
|
const ageNot23 = store.query.ids('author', () => ({
|
|
age: { neq: 23 },
|
|
}))
|
|
|
|
expect(ageNot23.value).toEqual(new Set([authors.davidMitchellFunny.id]))
|
|
})
|
|
|
|
it('supports records being added', () => {
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
const newAuthor = Author.create({ name: 'David Mitchell' })
|
|
store.put([newAuthor])
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id, newAuthor.id])
|
|
)
|
|
})
|
|
|
|
it('supports records being removed', () => {
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
store.remove([authors.davidMitchellFunny.id])
|
|
|
|
expect(mitchell.value).toEqual(new Set([authors.davidMitchellSerious.id]))
|
|
})
|
|
|
|
it('supports records being updated', () => {
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
age: { neq: 30 },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
|
|
|
|
expect(mitchell.value).toEqual(new Set([authors.davidMitchellSerious.id]))
|
|
|
|
store.put([{ ...authors.davidMitchellFunny, age: 23 }])
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellSerious.id, authors.davidMitchellFunny.id])
|
|
)
|
|
})
|
|
|
|
it('does not update if unrelated records are added, upated, or removed', () => {
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
const lastChangedEpoch = mitchell.lastChangedEpoch
|
|
|
|
const newAuthor = Author.create({ name: 'William Shakespeare' })
|
|
store.put([newAuthor])
|
|
|
|
mitchell.value
|
|
|
|
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
|
|
|
|
store.remove([authors.tolkein.id])
|
|
|
|
mitchell.value
|
|
|
|
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
|
|
})
|
|
|
|
it('doesnt change if related records are updated', () => {
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
const lastChangedEpoch = mitchell.lastChangedEpoch
|
|
|
|
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
|
|
|
|
mitchell.value
|
|
|
|
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
|
|
|
|
// make a change that does affect the query just to check
|
|
store.put([{ ...authors.davidMitchellFunny, name: 'steve' }])
|
|
|
|
mitchell.value
|
|
expect(mitchell.lastChangedEpoch).toBeGreaterThan(lastChangedEpoch)
|
|
})
|
|
|
|
it('supports items being removed and then added back', () => {
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
const lastChangedEpoch = mitchell.lastChangedEpoch
|
|
|
|
store.remove([authors.davidMitchellFunny.id])
|
|
store.put([authors.davidMitchellFunny])
|
|
|
|
mitchell.value
|
|
|
|
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
|
|
})
|
|
|
|
it('supports items being added and then removed', () => {
|
|
const mitchell = store.query.ids('author', () => ({
|
|
name: { eq: 'David Mitchell' },
|
|
}))
|
|
|
|
expect(mitchell.value).toEqual(
|
|
new Set([authors.davidMitchellFunny.id, authors.davidMitchellSerious.id])
|
|
)
|
|
|
|
const lastChangedEpoch = mitchell.lastChangedEpoch
|
|
|
|
const newMitchell = Author.create({ name: 'David Mitchell' })
|
|
|
|
store.put([newMitchell])
|
|
store.remove([newMitchell.id])
|
|
|
|
mitchell.value
|
|
|
|
expect(mitchell.lastChangedEpoch).toEqual(lastChangedEpoch)
|
|
})
|
|
})
|
|
|
|
const bookComparator = (a: Book, b: Book) => a.title.localeCompare(b.title)
|
|
|
|
describe('queries for records', () => {
|
|
it('can query for all values of a given type', () => {
|
|
const allBooks = store.query.records('book')
|
|
|
|
expect(allBooks.value.sort(bookComparator)).toEqual(
|
|
[books.cloudAtlas, books.farenheit, books.lotr, books.myLifeInComedy].sort(bookComparator)
|
|
)
|
|
|
|
const newBook = Book.create({ title: 'The Hobbit', authorId: authors.tolkein.id })
|
|
|
|
store.put([newBook])
|
|
|
|
expect(allBooks.value.sort(bookComparator)).toEqual(
|
|
[books.cloudAtlas, books.farenheit, books.lotr, books.myLifeInComedy, newBook].sort(
|
|
bookComparator
|
|
)
|
|
)
|
|
})
|
|
|
|
it('can query for a single value', () => {
|
|
const farenheit = store.query.records('book', () => ({
|
|
title: { eq: 'Farenheit 451' },
|
|
}))
|
|
|
|
expect(farenheit.value).toEqual([books.farenheit])
|
|
})
|
|
|
|
it('can query for multiple values', () => {
|
|
const funnyGuide = Book.create({
|
|
title: 'How to be a funny man',
|
|
authorId: authors.davidMitchellFunny.id,
|
|
})
|
|
store.put([funnyGuide])
|
|
const mitchell = store.query.records('book', () => ({
|
|
authorId: { eq: authors.davidMitchellFunny.id },
|
|
}))
|
|
|
|
expect(mitchell.value.sort(bookComparator)).toEqual(
|
|
[books.myLifeInComedy, funnyGuide].sort(bookComparator)
|
|
)
|
|
})
|
|
|
|
it('supports reactive queries', () => {
|
|
const currentAuthor = atom('currentAuthor', authors.davidMitchellFunny.id)
|
|
const booksQuery = store.query.records('book', () => ({
|
|
authorId: { eq: currentAuthor.value },
|
|
}))
|
|
|
|
expect(booksQuery.value).toEqual([books.myLifeInComedy])
|
|
|
|
currentAuthor.set(authors.tolkein.id)
|
|
|
|
expect(booksQuery.value).toEqual([books.lotr])
|
|
})
|
|
})
|
|
|
|
describe('filtering history', () => {
|
|
it('caches filters', () => {
|
|
const filter = store.query.filterHistory('author')
|
|
const filter2 = store.query.filterHistory('author')
|
|
|
|
expect(filter).toBe(filter2)
|
|
})
|
|
it('allows filtering history', () => {
|
|
const authorHistory = store.query.filterHistory('author')
|
|
|
|
authorHistory.value
|
|
|
|
let lastChangedEpoch = authorHistory.lastChangedEpoch
|
|
|
|
expect(authorHistory.getDiffSince(lastChangedEpoch - 1)).toBe(RESET_VALUE)
|
|
|
|
// updating an author should change the history
|
|
store.put([{ ...authors.davidMitchellFunny, age: 30 }])
|
|
|
|
authorHistory.value
|
|
|
|
expect(lastChangedEpoch).toBeLessThan(authorHistory.lastChangedEpoch)
|
|
|
|
expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([
|
|
{ updated: { [authors.davidMitchellFunny.id]: [{ age: 23 }, { age: 30 }] } },
|
|
])
|
|
|
|
lastChangedEpoch = authorHistory.lastChangedEpoch
|
|
|
|
// updating a book should not change the history
|
|
|
|
store.put([
|
|
{ ...books.lotr, title: 'The Lord of the Rings Part I: The Fellowship of the Ring' },
|
|
])
|
|
|
|
authorHistory.value
|
|
|
|
expect(authorHistory.lastChangedEpoch).toEqual(lastChangedEpoch)
|
|
expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([])
|
|
})
|
|
|
|
it('should not update if changes in a window of time cancel each other out', () => {
|
|
const authorHistory = store.query.filterHistory('author')
|
|
|
|
const epoch = authorHistory.value
|
|
const newAuthor = Author.create({ name: 'Stanley Briggs' })
|
|
|
|
store.put([newAuthor])
|
|
store.put([{ ...newAuthor, age: 38 }])
|
|
store.remove([newAuthor.id])
|
|
|
|
expect(authorHistory.value).toEqual(epoch)
|
|
|
|
store.remove([authors.tolkein.id])
|
|
store.put([authors.tolkein])
|
|
|
|
expect(authorHistory.value).toEqual(epoch)
|
|
})
|
|
|
|
it('removes update entries if a thing was deleted', () => {
|
|
const authorHistory = store.query.filterHistory('author')
|
|
|
|
authorHistory.value
|
|
|
|
const lastChangedEpoch = authorHistory.lastChangedEpoch
|
|
|
|
store.put([{ ...authors.davidMitchellFunny, age: 38 }])
|
|
store.put([{ ...authors.davidMitchellFunny, age: 343 }])
|
|
store.remove([authors.davidMitchellFunny.id])
|
|
|
|
expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([
|
|
{
|
|
// shows the original, not the updated version
|
|
removed: { [authors.davidMitchellFunny.id]: { age: 23 } },
|
|
},
|
|
])
|
|
})
|
|
|
|
it('collapses multiple updated entries into one', () => {
|
|
const authorHistory = store.query.filterHistory('author')
|
|
|
|
authorHistory.value
|
|
|
|
const lastChangedEpoch = authorHistory.lastChangedEpoch
|
|
|
|
store.put([{ ...authors.davidMitchellFunny, age: 38 }])
|
|
store.put([{ ...authors.davidMitchellFunny, age: 343 }])
|
|
|
|
expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([
|
|
{
|
|
updated: { [authors.davidMitchellFunny.id]: [{ age: 23 }, { age: 343 }] },
|
|
},
|
|
])
|
|
})
|
|
|
|
it('collapeses an add + update entry into just an add entry', () => {
|
|
const authorHistory = store.query.filterHistory('author')
|
|
|
|
authorHistory.value
|
|
|
|
const lastChangedEpoch = authorHistory.lastChangedEpoch
|
|
|
|
const newAuthor = Author.create({ name: 'Stanley Briggs' })
|
|
|
|
store.put([newAuthor])
|
|
store.put([{ ...newAuthor, age: 38 }])
|
|
|
|
expect(authorHistory.getDiffSince(lastChangedEpoch)).toMatchObject([
|
|
{
|
|
added: { [newAuthor.id]: { age: 38 } },
|
|
},
|
|
])
|
|
})
|
|
})
|