diff --git a/packages/dbx-firebase/src/lib/firestore/firestore.spec.ts b/packages/dbx-firebase/src/lib/firestore/firestore.spec.ts index 7b5d40bb7..551ba49c6 100644 --- a/packages/dbx-firebase/src/lib/firestore/firestore.spec.ts +++ b/packages/dbx-firebase/src/lib/firestore/firestore.spec.ts @@ -1,68 +1,47 @@ -import { - assertFails, - assertSucceeds, - initializeTestEnvironment, - RulesTestEnvironment, -} from "@firebase/rules-unit-testing"; - import { Firestore } from '@firebase/firestore'; -import { collection, CollectionReference, DocumentReference } from '@firebase/firestore'; -import { FirestoreDocument } from './document'; import { FirestoreCollection, makeFirestoreCollection } from './firestore'; +import { authorizedTestWithTestItemCollection, TestItem, testItemCollection, TestItemDocument } from "../../test/firebase.context.item"; -/** - * Data for a test item in our firestore collection. - */ -export interface TestItem { - -} +describe('FirestoreCollection', () => { -export class TestItemDocument implements FirestoreDocument { + authorizedTestWithTestItemCollection((f) => { - constructor(readonly documentRef: DocumentReference) { } + let firestore: Firestore; + let firestoreCollection: FirestoreCollection; -} + beforeEach(async () => { + firestore = f.parent.firestore; + firestoreCollection = makeFirestoreCollection({ + itemsPerPage: 50, + collection: testItemCollection(firestore), + makeDocument: (x) => new TestItemDocument(x.documentRef) + }); + }); -export const testItemCollectionPath = 'test'; + describe('makeFirestoreCollection()', () => { -export function testItemCollection(firestore: Firestore): CollectionReference { - return collection(firestore, testItemCollectionPath); -} + it('should create a new collection.', () => { -describe('FirestoreCollection', () => { + firestoreCollection = makeFirestoreCollection({ + itemsPerPage: 50, + collection: testItemCollection(firestore), + makeDocument: (x) => new TestItemDocument(x.documentRef) + }); - let testEnv: RulesTestEnvironment; - let firestore: Firestore; - let firestoreCollection: FirestoreCollection; + expect(firestoreCollection).toBeDefined(); + }); - beforeEach(async () => { - testEnv = await initializeTestEnvironment({ - // projectId: "demo-project-1234", - firestore: { - // rules: fs.readFileSync("firestore.rules", "utf8"), - }, }); - firestore = testEnv.authenticatedContext('test').firestore() as any as Firestore; - }); + describe('testItemCollection', () => { - afterEach(() => { - testEnv.clearFirestore(); - }); + it('should create a new document', () => { - describe('makeFirestoreCollection()', () => { + // TODO: - it('should create a new collection.', () => { - - firestoreCollection = makeFirestoreCollection({ - itemsPerPage: 50, - collection: testItemCollection(firestore), - makeDocument: (x) => new TestItemDocument(x.documentRef) }); - expect(firestoreCollection).toBeDefined(); }); - }); }); diff --git a/packages/dbx-firebase/src/test/firebase.context.item.spec.ts b/packages/dbx-firebase/src/test/firebase.context.item.spec.ts new file mode 100644 index 000000000..12af44421 --- /dev/null +++ b/packages/dbx-firebase/src/test/firebase.context.item.spec.ts @@ -0,0 +1,29 @@ +import { setDoc } from '@firebase/firestore'; +import { TestItemCollectionFixture, testWithTestItemFixture } from './firebase.context.item'; +import { authorizedFirebase } from './firebase.context'; +import { doc, getDoc } from 'firebase/firestore'; + +describe('testWithTestItemFixture', () => { + + const testWrapper = testWithTestItemFixture()(authorizedFirebase); + + testWrapper((f: TestItemCollectionFixture) => { + + it('should create a document', async () => { + + const documentRef = doc(f.instance.testItemCollection); + + await setDoc(documentRef, { + test: true + }); + + const snapshot = await getDoc(documentRef); + + expect(snapshot).toBeDefined(); + expect(snapshot.exists()).toBe(true); + + }); + + }); + +}); diff --git a/packages/dbx-firebase/src/test/firebase.context.item.ts b/packages/dbx-firebase/src/test/firebase.context.item.ts new file mode 100644 index 000000000..80b2634e0 --- /dev/null +++ b/packages/dbx-firebase/src/test/firebase.context.item.ts @@ -0,0 +1,79 @@ + +import { AbstractWrappedFixtureWithInstance, JestTestWrappedContextFactoryBuilder, instanceWrapJestTestContextFactory } from '@dereekb/util'; +import { Firestore, collection, CollectionReference, DocumentReference } from '@firebase/firestore'; +import { WithFieldValue, DocumentData, PartialWithFieldValue, SetOptions, QueryDocumentSnapshot, SnapshotOptions } from 'firebase/firestore'; +import { FirestoreDocument } from '../lib/firestore'; +import { FirebaseTestingContextFixture } from './firebase'; +import { authorizedFirebase } from './firebase.context'; + +// MARK: Test Item +/** + * Data for a test item in our firestore collection. + */ +export interface TestItem { + test?: boolean; +} + +export class TestItemDocument implements FirestoreDocument { + + constructor(readonly documentRef: DocumentReference) { } + +} + +export const testItemCollectionPath = 'test'; + +/** + * A way to build a testItemCollection from a firestore instance. + * + * @param firestore + * @returns + */ +export function testItemCollection(firestore: Firestore): CollectionReference { + return collection(firestore, testItemCollectionPath).withConverter({ + + // TODO: Change later? + + toFirestore(modelObject: WithFieldValue): DocumentData { + return { + test: false + }; + }, + fromFirestore(snapshot: QueryDocumentSnapshot, options?: SnapshotOptions): TestItem { + const data = snapshot.data(); + const result: TestItem = { test: data['test'] || false }; + return result; + } + }); +} + +// MARK: Test Item Testing Fixture +export class TestItemCollectionFixtureInstance { + + readonly testItemCollection = testItemCollection(this.fixture.parent.firestore); + + constructor(readonly fixture: TestItemCollectionFixture) { } + +} + +/** + * Used to expose a CollectionReference to TestItem for simple tests. + */ +export class TestItemCollectionFixture extends AbstractWrappedFixtureWithInstance { } + +export interface TestItemCollectionFirebaseContextConfig { } + +export function testWithTestItemFixture(config?: TestItemCollectionFirebaseContextConfig): JestTestWrappedContextFactoryBuilder { + return instanceWrapJestTestContextFactory({ + wrapFixture: (fixture) => new TestItemCollectionFixture(fixture), + makeInstance: (wrap) => new TestItemCollectionFixtureInstance(wrap), + teardownInstance: (instance: TestItemCollectionFixtureInstance) => { + // instance.fixture.parent.instance.clearFirestore(); + } + // TODO: Utilize config here using the setup/teardown later if needed. + }); +} + +/** + * Tests within an authorized context. + */ +export const authorizedTestWithTestItemCollection = testWithTestItemFixture()(authorizedFirebase); diff --git a/packages/dbx-firebase/src/test/firebase.context.ts b/packages/dbx-firebase/src/test/firebase.context.ts new file mode 100644 index 000000000..898339c9f --- /dev/null +++ b/packages/dbx-firebase/src/test/firebase.context.ts @@ -0,0 +1,29 @@ +import { JestTestContextFactory } from '@dereekb/util'; +import { firebaseTestBuilder, FirebaseTestingContextFixture } from './firebase'; + +import { getApp } from "firebase/app"; +// import { getFunctions, connectFunctionsEmulator } from "firebase/functions"; + +// const functions = getFunctions(getApp()); +// connectFunctionsEmulator(functions, "localhost", 5001); + +export const TESTING_AUTHORIZED_FIREBASE_USER_ID = '0'; + +export type FirebaseTestContextFactory = JestTestContextFactory; + +export const authorizedFirebase: FirebaseTestContextFactory = firebaseTestBuilder({ + testEnvironment: { + firestore: { + rules: `rules_version = '2'; + service cloud.firestore { + match /databases/{database}/documents { + match /{document=**} { + allow read, write: if true; + } + } + } + ` + } + }, + rulesContext: { userId: TESTING_AUTHORIZED_FIREBASE_USER_ID } +}); diff --git a/packages/dbx-firebase/src/test/firebase.ts b/packages/dbx-firebase/src/test/firebase.ts new file mode 100644 index 000000000..13c675ea8 --- /dev/null +++ b/packages/dbx-firebase/src/test/firebase.ts @@ -0,0 +1,88 @@ +import { Firestore } from '@firebase/firestore'; +import { + TestEnvironmentConfig, + initializeTestEnvironment, + RulesTestEnvironment, + RulesTestContext, + TokenOptions, +} from "@firebase/rules-unit-testing"; + +import { AbstractJestTestContextFixture, jestTestContextBuilder, JestTestContextFactory, Maybe } from "@dereekb/util"; + +// import { connectFirestoreEmulator } from "firebase/firestore"; + +export interface FirebaseTestingRulesContextConfig { + userId: string; + tokenOptions?: Maybe; +} + +export interface FirebaseTestingConfig { + testEnvironment: TestEnvironmentConfig; + rulesContext?: Maybe; +} + +export class FirebaseTestInstance { + + private readonly _firestore: Firestore = this.rulesTestContext.firestore() as any; + + constructor(readonly rulesTestEnvironment: RulesTestEnvironment, readonly rulesTestContext: RulesTestContext) { } + + get firestore(): Firestore { + return this._firestore; + } + + clearFirestore(): Promise { + return this.rulesTestEnvironment.clearFirestore(); + } + + // TODO: Add storage + +} + +export class FirebaseTestingContextFixture extends AbstractJestTestContextFixture { + + // MARK: From Instance + get firestore(): Firestore { + return this.instance.firestore; + } + +} + +/** + * A JestTestContextBuilderFunction for building firebase test context factories. + * + * This can be used to easily build a testing context that sets up RulesTestEnvironment for tests that sets itself up and tears itself down. + */ +export const firebaseTestBuilder = jestTestContextBuilder({ + buildConfig: (input?: Partial) => { + const config: FirebaseTestingConfig = { + testEnvironment: input?.testEnvironment ?? {}, + rulesContext: input?.rulesContext + }; + + return config; + }, + buildFixture: () => new FirebaseTestingContextFixture(), + setupInstance: async (config) => { + const rulesTestEnv = await initializeTestEnvironment(config.testEnvironment); + const rulesTestContext = rulesTestContextForConfig(rulesTestEnv, config.rulesContext); + return new FirebaseTestInstance(rulesTestEnv, rulesTestContext); + }, + teardownInstance: async (instance) => { + await instance.rulesTestEnvironment.cleanup(); // Cleanup + } +}); + +// MARK: Internal +function rulesTestContextForConfig(rulesTestEnv: RulesTestEnvironment, testingRulesConfig?: Maybe): RulesTestContext { + let rulesTestContext: RulesTestContext; + + if (testingRulesConfig != null) { + rulesTestContext = rulesTestEnv.authenticatedContext(testingRulesConfig.userId, testingRulesConfig.tokenOptions ?? undefined); + console.log('Authneticated?: ', rulesTestContext); + } else { + rulesTestContext = rulesTestEnv.unauthenticatedContext(); + } + + return rulesTestContext; +} diff --git a/packages/dbx-firebase/tsconfig.lib.json b/packages/dbx-firebase/tsconfig.lib.json index 898e5e622..81fb05f6e 100644 --- a/packages/dbx-firebase/tsconfig.lib.json +++ b/packages/dbx-firebase/tsconfig.lib.json @@ -5,7 +5,7 @@ "declaration": true, "declarationMap": true, "inlineSources": true, - "types": [] + "types": ["jest"] }, "exclude": ["src/test-setup.ts", "**/*.spec.ts", "**/*.test.ts"], "include": ["**/*.ts"] diff --git a/packages/dbx-firebase/tsconfig.spec.json b/packages/dbx-firebase/tsconfig.spec.json index e70b1ea78..5388e3f03 100644 --- a/packages/dbx-firebase/tsconfig.spec.json +++ b/packages/dbx-firebase/tsconfig.spec.json @@ -6,5 +6,5 @@ "types": ["jest", "node"] }, "files": ["src/test-setup.ts"], - "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts"] + "include": ["**/*.test.ts", "**/*.spec.ts", "**/*.d.ts", "src/test/firebase.context.ts"] } diff --git a/packages/util/src/index.ts b/packages/util/src/index.ts index f41a696fd..a30d2c723 100644 --- a/packages/util/src/index.ts +++ b/packages/util/src/index.ts @@ -1 +1,2 @@ export * from './lib'; +export * from './test'; diff --git a/packages/util/src/lib/context/context.ts b/packages/util/src/lib/context/context.ts new file mode 100644 index 000000000..55195bd21 --- /dev/null +++ b/packages/util/src/lib/context/context.ts @@ -0,0 +1,7 @@ +import { Initialized, Destroyable } from '../lifecycle'; + +// export type UseContext + +// export interface Context extends Initialized, Destroyable {} + +// export type ContextFactory = () => Context; diff --git a/packages/util/src/lib/context/index.ts b/packages/util/src/lib/context/index.ts new file mode 100644 index 000000000..e69de29bb diff --git a/packages/util/src/test/index.ts b/packages/util/src/test/index.ts new file mode 100644 index 000000000..a59f052e9 --- /dev/null +++ b/packages/util/src/test/index.ts @@ -0,0 +1,2 @@ +export * from './jest'; +export * from './jest.wrap'; diff --git a/packages/util/src/test/jest.spec.ts b/packages/util/src/test/jest.spec.ts new file mode 100644 index 000000000..63f4fed6c --- /dev/null +++ b/packages/util/src/test/jest.spec.ts @@ -0,0 +1,66 @@ +import { AbstractJestTestContextFixture, jestTestContextBuilder, JestTestContextBuilderFunction } from "./jest"; + +export interface TestConfig { + a: string; +} + +export class TestInstance { + constructor(readonly config?: TestConfig) { } +} + +export class TestJestTestContextFixture extends AbstractJestTestContextFixture { } + +export function makeTestBuilder() { + return jestTestContextBuilder({ + buildConfig: (input?: Partial) => ({ a: '0', ...input }), + buildFixture: () => new TestJestTestContextFixture(), + setupInstance: async (config) => new TestInstance(config), + teardownInstance: async () => undefined + }); +} + +describe('jestTestContextBuilder', () => { + + it('should return a builder function', () => { + + const testBuilder = makeTestBuilder(); + + expect(testBuilder).toBeDefined(); + expect(typeof testBuilder).toBe('function'); + }); + + describe('JestTestContextBuilderFunction', () => { + + const testBuilder: JestTestContextBuilderFunction = makeTestBuilder(); + + it('should create a new test context with no config provided.', () => { + const testContext = testBuilder(); + + expect(testContext).toBeDefined(); + expect(typeof testContext).toBe('function'); + }); + + describe('using test builder', () => { + + const testA = 'test'; + + testBuilder({ + a: testA + })((f) => { + + it('should be configured with the input configuration.', () => { + expect(f.instance.config!.a).toBe(testA); + }); + + it('should provide access to the instance via the fixture', () => { + expect(f.instance).toBeDefined(); + expect(f.instance instanceof TestInstance).toBe(true); + }); + + }); + + }); + + }) + +}); diff --git a/packages/util/src/test/jest.ts b/packages/util/src/test/jest.ts new file mode 100644 index 000000000..7108c687b --- /dev/null +++ b/packages/util/src/test/jest.ts @@ -0,0 +1,168 @@ + +/** + * A fixture instance that is generated new for each test run. + */ +export type JestTestFixtureInstance = I; + +/** + * The test fixture is used as a singleton across tests used in a single context. + * + * This allows us to define tests while referencing the instance. + */ +export interface JestTestFixture { + readonly instance: JestTestFixtureInstance; +} + +export type JestTestContextFixtureClearInstanceFunction = () => void; + +/** + * JestTestFixture with additional functions that the JestTestContextFactory sees for managing the instance. + */ +export interface JestTestContextFixture extends JestTestFixture { + + /** + * Sets the instance before the tests run, and returns a function to clean the instance later. + * + * If called again before the instance is finished being used, this should thrown an exception. + * + * @param instance + */ + setInstance(instance: I): JestTestContextFixtureClearInstanceFunction; + +} + +/** + * Abstract JestTestContextFixture instance. + */ +export abstract class AbstractJestTestContextFixture implements JestTestContextFixture { + + private _instance?: I; + + get instance(): I { + return this._instance!; + } + + setInstance(instance: I): JestTestContextFixtureClearInstanceFunction { + if (this._instance != null) { + throw new Error(`The testing fixture is locked. Don't call setInstance() directly.`); + } + + this._instance = instance; + + return () => { + delete this._instance; + } + } + +} + +export type JestBuildTestsWithContextFunction = (fixture: F) => void; + +/** + * Used for Jest tests to execute a number of tests using the fixture. + * + * The fixture is automatically setup and torn down each test per the configuration with a clean fixture instance. + */ +export type JestTestContextFactory = (buildTests: JestBuildTestsWithContextFunction) => void; + +/** + * Used to configure a JestTestContextFactory for building tests. + */ +export type JestTestContextBuilderFunction, C> = (config?: Partial) => JestTestContextFactory; + +export interface JestTestContextBuilderConfig, C> { + + /** + * Builds a config given the optional, partial input config. This is used across all tests. + */ + buildConfig: (config?: Partial) => C; + + /** + * Builds a new fixture to use across all tests encapsulated tests. + */ + buildFixture: (config: C) => F; + + /** + * Arbitrary before each function, called before the instance is setup. + */ + beforeEach?: () => Promise; + + /** + * Use for building an instance. + * + * When the promise resolves it should be ready to be used by the test being executed. + */ + setupInstance: (config: C) => Promise; + + /** + * Use for cleaning up the instance before the next test. + */ + teardownInstance: (instance: I, config: C) => Promise; + + /** + * Arbitrary after each function. + */ + afterEach?: () => Promise; +} + +/** + * Creates a JestTestContextBuilderFunction given the input builder. + * + * @param builder + * @returns + */ +export function jestTestContextBuilder, C>(builder: JestTestContextBuilderConfig): JestTestContextBuilderFunction { + return (inputConfig?: Partial) => { + const config = builder.buildConfig(inputConfig); + + return (buildTests: JestBuildTestsWithContextFunction) => { + const fixture = builder.buildFixture(config); + let instance: I; + let clearInstance: JestTestContextFixtureClearInstanceFunction; + + // Before + if (builder.beforeEach != null) { + beforeEach(builder.beforeEach); + } + + // Create an instance + beforeEach(async () => { + try { + instance = await builder.setupInstance(config); + clearInstance = fixture.setInstance(instance); + } catch (e) { + clearInstance(); + console.error('Failed building a test instance due to an error in buildInstance(). Error: ', e); + throw e; + } + }); + + /** + * Build tests by passing the fixture to the testing functions. + * + * This will inject all tests and sub Jest lifecycle items. + */ + buildTests(fixture); + + // Cleanup + afterEach(async () => { + if (fixture.instance == instance) { + clearInstance(); + } else if (fixture.instance != null) { + console.warn('Expected instance to be set on fixture for cleanup but was set to something else.'); + } + + try { + await builder.teardownInstance(instance, config); + } catch (e) { + console.error('Failed due to error in destroyingInstance()'); + throw e; + } + }); + + if (builder.afterEach != null) { + afterEach(builder.afterEach); + } + } + }; +} diff --git a/packages/util/src/test/jest.wrap.spec.ts b/packages/util/src/test/jest.wrap.spec.ts new file mode 100644 index 000000000..0646c8bb0 --- /dev/null +++ b/packages/util/src/test/jest.wrap.spec.ts @@ -0,0 +1,102 @@ +import { makeTestBuilder, TestJestTestContextFixture } from "./jest.spec"; +import { JestTestWrappedContextFactoryBuilder, wrapJestTestContextFactory } from "./jest.wrap"; + +export class WrappedTestJestTestContextFixture { + + constructor(readonly fixture: TestJestTestContextFixture) { } + +} + +export interface WrappedTestConfigureWrapperExampleConfig { + somePotentialConfig?: boolean; + onSetup?: () => void; + onTeardown?: (effect: number) => void; +} + +export function configureWrapperExample(config?: WrappedTestConfigureWrapperExampleConfig): JestTestWrappedContextFactoryBuilder { + return wrapJestTestContextFactory({ + wrapFixture: (fixture) => new WrappedTestJestTestContextFixture(fixture), + setupWrap: async (fixture: WrappedTestJestTestContextFixture) => { + + // Do nothing, but we could use the config here to initialize our new fixture for the tests it will be used it. + config?.onSetup?.(); + + // We return our effect. Is available within teardownWrap. + return 0; + }, + teardownWrap: async (fixture: WrappedTestJestTestContextFixture, effect: number) => { + + // Same here + config?.onTeardown?.(effect); + + } + }); +} + +describe('wrapJestTestContextFactory()', () => { + + const testBuilder = makeTestBuilder(); + + function makeWrapper() { + return configureWrapperExample({ + somePotentialConfig: true + }); + } + + it('should create a function for wrapping a factory.', () => { + const wrapper = makeWrapper(); + + expect(wrapper).toBeDefined(); + expect(typeof wrapper).toBe('function'); + }); + + describe('wrapper', () => { + + let wasSetup = false; + + const wrapper = configureWrapperExample({ + // Shows how we can configure the wrapper to do additional setup/teardown or add arbitrary interactions. + onSetup: () => { + wasSetup = true; + }, + onTeardown: () => { + wasSetup = false; + } + }); + + it('should wrap a factory', () => { + const factory = wrapper(testBuilder()) + + expect(factory).toBeDefined(); + expect(typeof factory).toBe('function'); + }); + + describe('wrapped factory', () => { + + const wrappedFactory = wrapper(testBuilder()); + + describe('with tests executed within test context', () => { + + wrappedFactory(() => { + + it('should execute our setup wrap', () => { + expect(wasSetup).toBe(true); + }); + + }); + + }); + + describe('with tests executed outside test context', () => { + + it('should not execute our setup wrap', () => { + expect(wasSetup).toBe(false); + }); + + }); + + }); + + }); + +}); diff --git a/packages/util/src/test/jest.wrap.ts b/packages/util/src/test/jest.wrap.ts new file mode 100644 index 000000000..1ee83b821 --- /dev/null +++ b/packages/util/src/test/jest.wrap.ts @@ -0,0 +1,124 @@ +import { AbstractJestTestContextFixture, JestBuildTestsWithContextFunction, JestTestContextFactory, JestTestContextFixtureClearInstanceFunction } from "./jest"; + +export abstract class AbstractWrappedFixture { + + constructor(readonly fixture: F) { } + +} + +export abstract class AbstractWrappedFixtureWithInstance extends AbstractJestTestContextFixture { + + constructor(readonly parent: F) { + super(); + } + +} + +/** + * Used to wrap a JestTestContextFactory of one fixture type to another. + * + * This is useful for cases where the base fixture may be used in a lot of places and contexts, but the wrapped version can configure + * tests more specifically. + */ +export type JestTestWrappedContextFactoryBuilder = (factory: JestTestContextFactory) => JestTestContextFactory; + +export interface JestWrapTestContextConfig { + + /** + * Wraps the fixture. This occurs once before any tests execute. + */ + wrapFixture: (fixture: F) => W; + + /** + * Use for doing any setup that may be required on a per-test basis. + * + * This occurs before every test, but after the fixture's instance has already been configured. + * + * The setup can return an effect. This effect is passed to the teardown function later, if provided. + */ + setupWrap?: (wrap: W) => Promise; + + /** + * Use for cleaning up the instance before the next function. + * + * This occurs after every test, but after the fixture's instance has already been configured. + */ + teardownWrap?: (wrap: W, effect: E) => Promise; + +} + +/** + * Wraps the input JestTestContextFactory to emit another type of Fixture for tests. + * + * @returns + */ +export function wrapJestTestContextFactory(config: JestWrapTestContextConfig): (factory: JestTestContextFactory) => JestTestContextFactory { + return (factory: JestTestContextFactory) => { + return (buildTests: JestBuildTestsWithContextFunction) => { + factory((inputFixture: F) => { + const wrap = config.wrapFixture(inputFixture); + let effect: E; + + if (config.setupWrap != null) { + beforeEach(async () => { + effect = await config.setupWrap!(wrap); + }); + } + + buildTests(wrap); + + if (config.teardownWrap != null) { + afterEach(async () => { + await config.teardownWrap!(wrap, effect); + }); + } + + }); + }; + } +} + +// MARK EasyWrap +export interface InstanceJestWrapTestContextConfig, F> extends Pick, 'wrapFixture'> { + + /** + * Creates a new instance for the tests. + */ + makeInstance: (wrap: W) => I | Promise; + + /** + * Use for doing any setup that may be required on a per-test basis. + * + * This occurs before every test, but after the fixture's instance has already been configured. + */ + setupInstance?: (instance: I, wrap: W) => void | Promise; + + /** + * Use for cleaning up the instance before the next function. + * + * This occurs after every test, but after the fixture's instance has already been configured. + */ + teardownInstance?: (instance: I) => void | Promise; + +} + +export function instanceWrapJestTestContextFactory, F>(config: InstanceJestWrapTestContextConfig): (factory: JestTestContextFactory) => JestTestContextFactory { + return wrapJestTestContextFactory({ + wrapFixture: config.wrapFixture, + setupWrap: async (wrap: W) => { + const instance = await config.makeInstance(wrap); + const effect = wrap.setInstance(instance); + + if (config.setupInstance) { + await config.setupInstance(instance, wrap); + } + + return effect; + }, + teardownWrap: async (wrap: W) => { + if (config.teardownInstance) { + await config.teardownInstance(wrap.instance); + } + } + }); +} diff --git a/packages/util/tsconfig.lib.json b/packages/util/tsconfig.lib.json index 6efdbeecb..8c8212ab2 100644 --- a/packages/util/tsconfig.lib.json +++ b/packages/util/tsconfig.lib.json @@ -4,7 +4,7 @@ "module": "commonjs", "outDir": "../../dist/out-tsc", "declaration": true, - "types": ["node"] + "types": ["jest", "node"] }, "exclude": ["**/*.spec.ts", "**/*.test.ts"], "include": ["**/*.ts"]