ElectronicBabylonianLiterature/ebl-frontend

View on GitHub
src/test-support/AppDriver.tsx

Summary

Maintainability
A
0 mins
Test Coverage
A
92%
import React from 'react'
import {
  fireEvent,
  render,
  RenderResult,
  screen,
  Matcher,
  within,
  waitFor,
  ByRoleMatcher,
} from '@testing-library/react'
import userEvent from '@testing-library/user-event'
import { MemoryRouter } from 'react-router-dom'
import _ from 'lodash'
import App from 'App'
import WordRepository from 'dictionary/infrastructure/WordRepository'
import FragmentRepository from 'fragmentarium/infrastructure/FragmentRepository'
import ApiImageRepository from 'fragmentarium/infrastructure/ImageRepository'
import FragmentService from 'fragmentarium/application/FragmentService'
import WordService from 'dictionary/application/WordService'
import TextService from 'corpus/application/TextService'
import MemorySession, { Session, guestSession } from 'auth/Session'
import BibliographyRepository from 'bibliography/infrastructure/BibliographyRepository'
import BibliographyService from 'bibliography/application/BibliographyService'
import FragmentSearchService from 'fragmentarium/application/FragmentSearchService'
import { Promise } from 'bluebird'
import { eblNameProperty, AuthenticationContext } from 'auth/Auth'
import SignRepository from 'signs/infrastructure/SignRepository'
import SignService from 'signs/application/SignService'
import MarkupService, {
  CachedMarkupService,
} from 'markup/application/MarkupService'
import AfoRegisterRepository from 'afo-register/infrastructure/AfoRegisterRepository'
import AfoRegisterService from 'afo-register/application/AfoRegisterService'
import { FindspotService } from 'fragmentarium/application/FindspotService'
import { ApiFindspotRepository } from 'fragmentarium/infrastructure/FindspotRepository'
import FakeApi from 'test-support/FakeApi'

export function getServices(
  api: any = FakeApi
): {
  signService: SignService
  wordService: WordService
  fragmentService: FragmentService
  fragmentSearchService: FragmentSearchService
  bibliographyService: BibliographyService
  textService: TextService
  markupService: MarkupService
  cachedMarkupService: CachedMarkupService
  afoRegisterService: AfoRegisterService
  findspotService: FindspotService
} {
  const wordRepository = new WordRepository(api)
  const fragmentRepository = new FragmentRepository(api)
  const imageRepository = new ApiImageRepository(api)
  const bibliographyRepository = new BibliographyRepository(api)
  const findspotRepository = new ApiFindspotRepository(api)

  const wordService = new WordService(wordRepository)
  const bibliographyService = new BibliographyService(bibliographyRepository)
  const fragmentService = new FragmentService(
    fragmentRepository,
    imageRepository,
    wordRepository,
    bibliographyService
  )
  const fragmentSearchService = new FragmentSearchService(fragmentRepository)
  const textService = new TextService(
    api,
    fragmentService,
    wordService,
    bibliographyService
  )
  const signsRepository = new SignRepository(api)
  const afoRegisterRepository = new AfoRegisterRepository(api)
  const signService = new SignService(signsRepository)
  const markupService = new MarkupService(api, bibliographyService)
  const cachedMarkupService = new CachedMarkupService(api, bibliographyService)
  const afoRegisterService = new AfoRegisterService(afoRegisterRepository)
  const findspotService = new FindspotService(findspotRepository)
  return {
    signService,
    wordService,
    fragmentService,
    fragmentSearchService,
    bibliographyService,
    textService,
    markupService,
    cachedMarkupService,
    afoRegisterService,
    findspotService,
  }
}

function createApp(api): JSX.Element {
  return <App {...getServices(api)} />
}

const breadcrumbs = {
  getBreadcrumbs(): HTMLElement {
    return screen.getByRole('navigation', {
      name: 'breadcrumb',
    })
  },

  expectCrumbs(crumbs: readonly string[]): void {
    const crumbsElement = this.getBreadcrumbs()
    for (const crumb of crumbs) {
      expect(within(crumbsElement).getByText(crumb)).toBeInTheDocument()
    }
  },

  expectCrumb(crumb: string, link: string): void {
    expect(
      within(this.getBreadcrumbs()).getByRole('link', { name: crumb })
    ).toHaveAttribute('href', link)
  },
} as const

export default class AppDriver {
  readonly breadcrumbs = breadcrumbs

  private initialEntries: string[] = []
  private view: RenderResult | null = null
  private session: Session | null = null

  constructor(private readonly api) {}

  getView(): RenderResult {
    if (this.view) {
      return this.view
    } else {
      throw new Error('getElement called before render.')
    }
  }

  withPath(path: string): AppDriver {
    this.initialEntries = [path]
    return this
  }

  withSession(): AppDriver {
    this.session = new MemorySession([
      'read:texts',
      'write:texts',
      'read:fragments',
      'annotate:fragments',
      'read:words',
    ])
    return this
  }

  render(): AppDriver {
    this.view = render(
      <MemoryRouter initialEntries={this.initialEntries}>
        <AuthenticationContext.Provider
          value={{
            login: _.noop,
            logout: _.noop,
            getSession: (): Session => this.session ?? guestSession,
            isAuthenticated: (): boolean => this.session !== null,
            getAccessToken(): Promise<string> {
              throw new Error('Not implemented')
            },
            getUser(): { [eblNameProperty]: string } {
              return { [eblNameProperty]: 'Test' }
            },
          }}
        >
          {createApp(this.api)}
        </AuthenticationContext.Provider>
      </MemoryRouter>
    )

    return this
  }

  async waitForText(text: Matcher): Promise<void> {
    await this.getView().findAllByText(text)
  }

  async waitForTextToDisappear(text: Matcher): Promise<void> {
    await waitFor(() => {
      this.expectNotInContent(text)
    })
  }

  expectTextContent(text: string | RegExp): void {
    expect(this.getView().container).toHaveTextContent(text)
  }

  expectNotInContent(text: Matcher): void {
    expect(this.getView().queryByText(text)).not.toBeInTheDocument()
  }

  expectLink(text: Matcher, expectedHref: string): void {
    expect(this.getView().getByText(text)).toHaveAttribute('href', expectedHref)
  }

  expectInputElement(label: Matcher, expectedValue: unknown): void {
    expect(this.getView().getByLabelText(label)).toHaveValue(
      String(expectedValue)
    )
  }

  expectChecked(label: Matcher): void {
    expect(this.getView().getByLabelText(label)).toBeChecked()
  }

  expectNotChecked(label: Matcher): void {
    expect(this.getView().getByLabelText(label)).not.toBeChecked()
  }

  changeValueByLabel(label: Matcher, newValue: string): void {
    const input = this.getView().getByLabelText(label)
    fireEvent.change(input, { target: { value: newValue } })
  }

  click(text: Matcher, n = 0): void {
    const clickable = this.getView().getAllByText(text)[n]
    userEvent.click(clickable)
  }

  clickByRole(role: ByRoleMatcher, name: string | RegExp, n = 0): void {
    const clickable = this.getView().getAllByRole(role, { name })[n]
    userEvent.click(clickable)
  }
}