import type { Applog, ApplogForInsertOptionalAgent, sortApplogsByTs } from '@wovin/core/applog' import type { WriteableThread } from '@wovin/core/thread/writeable' import { Thread } from '@wovin/core' import { _resetGlobalState, autorun, configure } from '@wovin/core/mobx' import { ThreadInMemory } from '@wovin/core/thread/writeable' import { omit } from 'lodash-es' import { afterEach, beforeEach, expect } from 'vitest' import { testMovieAtoms } from '../../test-applogs' configure({ disableErrorBoundaries: true }) // requires _resetGlobalState() in afterEach interface CustomMatchers { toBeApplogs: (expected: ApplogForInsertOptionalAgent[]) => R } declare module 'vitest' { interface Assertion extends CustomMatchers {} interface AsymmetricMatchersContaining extends CustomMatchers {} export interface TestContext { db: WriteableThread autorunAndReturn: (fn: () => R, opts?: Parameters[1]) => () => R insert: (applogs: ApplogForInsertOptionalAgent[], db?: WriteableThread) => void disposers: Array<() => void> } } // interface MyFixtures { // db: Thread // autorun: typeof autorun // disposers: Array<() => void> // } // const testExtended = test.extend({ // db: null, // autorun: null, // disposers: [], // }) beforeEach(async (context) => { context.disposers = [] const testLogs = [...testMovieAtoms] sortApplogsByTs(testLogs) // context.db = new ThreadIDB([...testMovieAtoms], 'test-movielogs') context.db = ThreadInMemory.fromArray(testLogs, 'test-movielogs') context.autorunAndReturn = (fn, opts) => { let ret: R const disposer = autorun(() => { ret = fn() }, opts) context.disposers.push(disposer) return () => ret } context.insert = (applogs, db = context.db) => { db.insert(applogs.map(applog => ({ ag: 'demoAgent', ...applog }))) } }) afterEach((context) => { _resetGlobalState() context.disposers.forEach(d => d()) context.disposers = [] }) ////////////// // MATCHERS // ////////////// expect.extend({ toBeApplogs(received: Applog[] | Thread, expected: ApplogForInsertOptionalAgent[]) { const { isNot } = this if (received instanceof Thread) received = received.applogs expect(received).to.be.an('array') const sanitizedReceived = received.map((receivedObj) => { const omitFields = [] if (receivedObj.ag === 'demoAgent') { omitFields.push('ag') } // If the expected object doesn't have a 'ts' field, remove it from the received object if (!expected.some(expectedObj => 'ts' in expectedObj)) { omitFields.push('ts') } return omitFields.length ? omit(receivedObj, omitFields) : receivedObj }) const sanitizedExpected = expected.map((expectedObj) => { const omitFields = [] if (expectedObj.ag === 'demoAgent') { omitFields.push('ag') } return omitFields.length ? omit(expectedObj, omitFields) : expectedObj }) const pass = expect(sanitizedReceived).to.have.deep.members(sanitizedExpected) // HACK: this is chai syntax but we should return a boolean return { // do not alter your "pass" based on isNot. Vitest does it for you pass, message: () => `Applogs are${isNot ? ' not' : ''} matching`, } }, }) // export function matchApplog = (receivedObj: Applog, expectedObj: ApplogForInsert) => { // if (!expectedObj.ts) { // receivedObj = omit(receivedObj, 'ts') // } // expect(receivedObj).to.have.deep.members([ // expectedObj // ]) // };