282 lines
11 KiB
JavaScript
282 lines
11 KiB
JavaScript
import { ALL_EMOJI, ALL_EMOJI_NO_ETAG, basicAfterEach, basicBeforeEach, tick, truncatedEmoji } from '../shared'
|
|
import Database from '../../../src/database/Database'
|
|
import allEmoji from 'emoji-picker-element-data/en/emojibase/data.json'
|
|
import { mockGetAndHead } from '../mockFetch.js'
|
|
|
|
function mockEmoji (dataSource, data, etag) {
|
|
fetch.reset()
|
|
mockGetAndHead(dataSource, data, etag && { headers: { ETag: etag } })
|
|
}
|
|
|
|
async function testDataChange (firstData, secondData, firstCallback, secondCallback, thirdCallback) {
|
|
const dataSource = 'http://localhost/will-change.json'
|
|
|
|
// first time - data is v1
|
|
mockEmoji(dataSource, firstData, 'v1')
|
|
|
|
let db = new Database({ dataSource })
|
|
await db.ready()
|
|
await firstCallback(db, dataSource)
|
|
await db.close()
|
|
|
|
// second time - update, data is v2
|
|
mockEmoji(dataSource, secondData, 'v2')
|
|
|
|
db = new Database({ dataSource })
|
|
await db.ready()
|
|
await db._lazyUpdate
|
|
await secondCallback(db, dataSource)
|
|
await db.close()
|
|
|
|
// third time - no update, data is v2
|
|
mockEmoji(dataSource, secondData, 'v2')
|
|
|
|
db = new Database({ dataSource })
|
|
await db.ready()
|
|
await db._lazyUpdate
|
|
|
|
await thirdCallback(db, dataSource)
|
|
await db.delete()
|
|
}
|
|
|
|
describe('database second load and update', () => {
|
|
beforeEach(basicBeforeEach)
|
|
afterEach(basicAfterEach)
|
|
|
|
test('updates emoji on second load if changed', async () => {
|
|
const changedEmoji = JSON.parse(JSON.stringify(truncatedEmoji))
|
|
const roflIndex = allEmoji.findIndex(_ => _.annotation === 'rolling on the floor laughing')
|
|
changedEmoji[roflIndex] = allEmoji.find(_ => _.annotation === 'pineapple') // replace rofl
|
|
|
|
await testDataChange(truncatedEmoji, changedEmoji, async (db, dataSource) => {
|
|
// first load
|
|
expect(fetch.calls().length).toBe(1)
|
|
expect(fetch.lastUrl()).toBe(dataSource)
|
|
expect(fetch.lastOptions()).toBe(undefined)
|
|
expect((await db.getEmojiByShortcode('rofl')).annotation).toBe('rolling on the floor laughing')
|
|
expect(await db.getEmojiByShortcode('weary_cat')).toBeFalsy()
|
|
}, async (db, dataSource) => {
|
|
// second load
|
|
expect(fetch.calls().length).toBe(2)
|
|
expect(fetch.lastUrl()).toBe(dataSource)
|
|
expect(fetch.lastOptions()).toBe(undefined)
|
|
expect(fetch.calls().at(-2)[0]).toBe(dataSource)
|
|
expect(fetch.calls().at(-2)[1]).toEqual({ method: 'HEAD' })
|
|
expect((await db.getEmojiByShortcode('rofl'))).toBeFalsy()
|
|
expect((await db.getEmojiByShortcode('pineapple')).annotation).toBe('pineapple')
|
|
}, async (db, dataSource) => {
|
|
// third load
|
|
expect(fetch.calls().length).toBe(1)
|
|
expect(fetch.lastUrl()).toBe(dataSource)
|
|
expect(fetch.lastOptions()).toEqual({ method: 'HEAD' })
|
|
expect((await db.getEmojiByShortcode('rofl'))).toBeFalsy()
|
|
expect((await db.getEmojiByShortcode('pineapple')).annotation).toBe('pineapple')
|
|
})
|
|
})
|
|
|
|
test('updates with deleted emoji', async () => {
|
|
const noMonkey = JSON.parse(JSON.stringify(truncatedEmoji))
|
|
const idx = noMonkey.findIndex(_ => _.annotation === 'monkey face')
|
|
noMonkey.splice(idx, 1) // remove monkey face
|
|
|
|
await testDataChange(truncatedEmoji, noMonkey, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.annotation)).toStrictEqual([
|
|
'monkey face'
|
|
])
|
|
}, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.annotation)).toStrictEqual([])
|
|
}, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.annotation)).toStrictEqual([])
|
|
})
|
|
})
|
|
|
|
test('updates with added emoji', async () => {
|
|
const noMonkey = JSON.parse(JSON.stringify(truncatedEmoji))
|
|
const idx = noMonkey.findIndex(_ => _.annotation === 'monkey face')
|
|
noMonkey.splice(idx, 1) // remove monkey face
|
|
|
|
await testDataChange(noMonkey, truncatedEmoji, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.annotation)).toStrictEqual([])
|
|
}, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.annotation)).toStrictEqual([
|
|
'monkey face'
|
|
])
|
|
}, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.annotation)).toStrictEqual([
|
|
'monkey face'
|
|
])
|
|
})
|
|
})
|
|
|
|
test('updates with modified emoji', async () => {
|
|
const changedMonkey = JSON.parse(JSON.stringify(truncatedEmoji))
|
|
const idx = changedMonkey.findIndex(_ => _.annotation === 'monkey face')
|
|
changedMonkey[idx].shortcodes.push('fake_monkey_shortcode')
|
|
|
|
await testDataChange(truncatedEmoji, changedMonkey, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.shortcodes)).toStrictEqual([
|
|
['monkey_face']
|
|
])
|
|
}, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.shortcodes)).toStrictEqual([
|
|
['monkey_face', 'fake_monkey_shortcode']
|
|
])
|
|
}, async (db) => {
|
|
expect((await db.getEmojiBySearchQuery('monkey face')).map(_ => _.shortcodes)).toStrictEqual([
|
|
['monkey_face', 'fake_monkey_shortcode']
|
|
])
|
|
})
|
|
})
|
|
|
|
test('updates emoji on second load if changed - no ETag', async () => {
|
|
const dataSource = 'http://localhost/will-change.json'
|
|
|
|
// first time - data is v1
|
|
mockEmoji(dataSource, truncatedEmoji)
|
|
|
|
let db = new Database({ dataSource })
|
|
await db.ready()
|
|
expect(fetch.calls().length).toBe(1)
|
|
expect(fetch.lastUrl()).toBe(dataSource)
|
|
expect(fetch.lastOptions()).toBe(undefined)
|
|
|
|
expect((await db.getEmojiByShortcode('rofl')).annotation).toBe('rolling on the floor laughing')
|
|
expect(await db.getEmojiByShortcode('weary_cat')).toBeFalsy()
|
|
|
|
await db.close()
|
|
|
|
const changedEmoji = JSON.parse(JSON.stringify(truncatedEmoji))
|
|
const roflIndex = allEmoji.findIndex(_ => _.annotation === 'rolling on the floor laughing')
|
|
changedEmoji[roflIndex] = allEmoji.find(_ => _.annotation === 'pineapple') // replace rofl
|
|
|
|
// second time - update, data is v2
|
|
mockEmoji(dataSource, changedEmoji)
|
|
|
|
db = new Database({ dataSource })
|
|
await db.ready()
|
|
await db._lazyUpdate
|
|
expect(fetch.calls().length).toBe(2)
|
|
expect(fetch.lastUrl()).toBe(dataSource)
|
|
expect(fetch.lastOptions()).toBe(undefined)
|
|
expect(fetch.calls().at(-2)[0]).toBe(dataSource)
|
|
expect(fetch.calls().at(-2)[1]).toEqual({ method: 'HEAD' })
|
|
expect((await db.getEmojiByShortcode('rofl'))).toBeFalsy()
|
|
expect((await db.getEmojiByShortcode('pineapple')).annotation).toBe('pineapple')
|
|
await db.close()
|
|
|
|
// third time - no update, data is v2
|
|
mockEmoji(dataSource, changedEmoji)
|
|
|
|
db = new Database({ dataSource })
|
|
await db.ready()
|
|
await db._lazyUpdate
|
|
expect(fetch.calls().length).toBe(2)
|
|
expect(fetch.lastUrl()).toBe(dataSource)
|
|
expect(fetch.lastOptions()).toBe(undefined)
|
|
expect(fetch.calls().at(-2)[0]).toBe(dataSource)
|
|
expect(fetch.calls().at(-2)[1]).toEqual({ method: 'HEAD' })
|
|
expect((await db.getEmojiByShortcode('rofl'))).toBeFalsy()
|
|
expect((await db.getEmojiByShortcode('pineapple')).annotation).toBe('pineapple')
|
|
await db.delete()
|
|
})
|
|
|
|
test('URL change causes an update', async () => {
|
|
const dataSource = 'http://localhost/will-change.json'
|
|
const dataSource2 = 'http://localhost/will-change2.json'
|
|
|
|
// first time - data is v1
|
|
mockGetAndHead(dataSource, truncatedEmoji, { headers: { ETag: 'W/xxx' } })
|
|
|
|
let db = new Database({ dataSource })
|
|
await db.ready()
|
|
expect(fetch.calls().length).toBe(1)
|
|
expect(fetch.lastUrl()).toBe(dataSource)
|
|
expect(fetch.lastOptions()).toBe(undefined)
|
|
|
|
expect((await db.getEmojiByShortcode('rofl')).annotation).toBe('rolling on the floor laughing')
|
|
expect(await db.getEmojiByShortcode('weary_cat')).toBeFalsy()
|
|
|
|
await db.close()
|
|
|
|
const changedEmoji = JSON.parse(JSON.stringify(truncatedEmoji))
|
|
const roflIndex = allEmoji.findIndex(_ => _.annotation === 'rolling on the floor laughing')
|
|
changedEmoji[roflIndex] = allEmoji.find(_ => _.annotation === 'pineapple') // replace rofl
|
|
|
|
// second time - update, data is v2
|
|
fetch.reset()
|
|
mockGetAndHead(dataSource2, changedEmoji, { headers: { ETag: 'W/yyy' } })
|
|
|
|
db = new Database({ dataSource: dataSource2 })
|
|
await db.ready()
|
|
await db._lazyUpdate
|
|
expect(fetch.calls().length).toBe(2)
|
|
expect(fetch.lastUrl()).toBe(dataSource2)
|
|
expect(fetch.lastOptions()).toBe(undefined)
|
|
expect(fetch.calls().at(-2)[0]).toBe(dataSource2)
|
|
expect(fetch.calls().at(-2)[1]).toEqual({ method: 'HEAD' })
|
|
expect((await db.getEmojiByShortcode('rofl'))).toBeFalsy()
|
|
expect((await db.getEmojiByShortcode('pineapple')).annotation).toBe('pineapple')
|
|
|
|
// third time - no update, data is v2
|
|
fetch.reset()
|
|
mockGetAndHead(dataSource2, changedEmoji, { headers: { ETag: 'W/yyy' } })
|
|
|
|
db = new Database({ dataSource: dataSource2 })
|
|
await db.ready()
|
|
await db._lazyUpdate
|
|
expect(fetch.calls().length).toBe(1)
|
|
expect(fetch.lastUrl()).toBe(dataSource2)
|
|
expect(fetch.lastOptions()).toEqual({ method: 'HEAD' })
|
|
expect((await db.getEmojiByShortcode('rofl'))).toBeFalsy()
|
|
expect((await db.getEmojiByShortcode('pineapple')).annotation).toBe('pineapple')
|
|
|
|
await db.delete()
|
|
})
|
|
|
|
test('URL changed but ETag did not should still cause it to re-populate', async () => {
|
|
const otherSource = 'http://localhost/other.json'
|
|
|
|
let db = new Database({ dataSource: ALL_EMOJI_NO_ETAG })
|
|
await db.close()
|
|
mockEmoji(otherSource, truncatedEmoji, 'W/xxx')
|
|
|
|
db = new Database({ dataSource: otherSource })
|
|
await db.ready()
|
|
await tick(5) // the request is done asynchronously, so wait for it
|
|
expect(fetch.calls().length).toBe(2)
|
|
expect(fetch.calls().at(-2)[0]).toBe(otherSource)
|
|
expect(fetch.calls().at(-2)[1]).toEqual({ method: 'HEAD' })
|
|
expect(fetch.lastUrl()).toBe(otherSource)
|
|
expect(fetch.lastOptions()).toBe(undefined)
|
|
await db.delete()
|
|
})
|
|
|
|
test('ETag changed, database updates happen concurrently', async () => {
|
|
const db = new Database({ dataSource: ALL_EMOJI })
|
|
await db.close()
|
|
|
|
const changedEmoji = JSON.parse(JSON.stringify(truncatedEmoji))
|
|
const roflIndex = allEmoji.findIndex(_ => _.annotation === 'rolling on the floor laughing')
|
|
changedEmoji[roflIndex] = allEmoji.find(_ => _.annotation === 'pineapple') // replace rofl
|
|
|
|
// second time - update, data is v2
|
|
fetch.reset()
|
|
mockGetAndHead(ALL_EMOJI, changedEmoji, { headers: { ETag: 'W/yyy' } })
|
|
|
|
// open two at once
|
|
const dbs = [
|
|
new Database({ dataSource: ALL_EMOJI }),
|
|
new Database({ dataSource: ALL_EMOJI })
|
|
]
|
|
|
|
for (const otherDB of dbs) {
|
|
await otherDB.ready()
|
|
await otherDB._lazyUpdate
|
|
expect((await otherDB.getEmojiByShortcode('rofl'))).toBeFalsy()
|
|
expect((await otherDB.getEmojiByShortcode('pineapple')).annotation).toBe('pineapple')
|
|
}
|
|
|
|
await db.delete()
|
|
})
|
|
})
|