import { DatePipe } from '@angular/common'
import {
  HttpErrorResponse,
  provideHttpClient,
  withInterceptorsFromDi,
} from '@angular/common/http'
import { provideHttpClientTesting } from '@angular/common/http/testing'
import { ComponentFixture, TestBed } from '@angular/core/testing'
import { By } from '@angular/platform-browser'
import { ActivatedRoute, Router, convertToParamMap } from '@angular/router'
import { RouterTestingModule } from '@angular/router/testing'
import {
  NgbDropdown,
  NgbDropdownItem,
  NgbModal,
  NgbModalRef,
} from '@ng-bootstrap/ng-bootstrap'
import { NgxBootstrapIconsModule, allIcons } from 'ngx-bootstrap-icons'
import { provideUiTour } from 'ngx-ui-tour-ng-bootstrap'
import { Subject, of, throwError } from 'rxjs'
import { routes } from 'src/app/app-routing.module'
import {
  DEFAULT_DISPLAY_FIELDS,
  DisplayField,
  DisplayMode,
  Document,
} from 'src/app/data/document'
import {
  FILTER_FULLTEXT_MORELIKE,
  FILTER_FULLTEXT_QUERY,
  FILTER_HAS_TAGS_ANY,
} from 'src/app/data/filter-rule-type'
import { SavedView } from 'src/app/data/saved-view'
import { SETTINGS_KEYS } from 'src/app/data/ui-settings'
import { SortableDirective } from 'src/app/directives/sortable.directive'
import { PermissionsGuard } from 'src/app/guards/permissions.guard'
import { CustomDatePipe } from 'src/app/pipes/custom-date.pipe'
import { DocumentTitlePipe } from 'src/app/pipes/document-title.pipe'
import { FilterPipe } from 'src/app/pipes/filter.pipe'
import { UsernamePipe } from 'src/app/pipes/username.pipe'
import { DocumentListViewService } from 'src/app/services/document-list-view.service'
import { PermissionsService } from 'src/app/services/permissions.service'
import { DocumentService } from 'src/app/services/rest/document.service'
import { SavedViewService } from 'src/app/services/rest/saved-view.service'
import { SettingsService } from 'src/app/services/settings.service'
import { ToastService } from 'src/app/services/toast.service'
import {
  FileStatus,
  WebsocketStatusService,
} from 'src/app/services/websocket-status.service'
import { DocumentCardLargeComponent } from './document-card-large/document-card-large.component'
import { DocumentCardSmallComponent } from './document-card-small/document-card-small.component'
import { DocumentListComponent } from './document-list.component'

const docs: Document[] = [
  {
    id: 1,
    title: 'Doc1',
    notes: [],
    tags: [],
    content: 'document content 1',
  },
  {
    id: 2,
    title: 'Doc2',
    notes: [],
    tags: [],
    content: 'document content 2',
  },
  {
    id: 3,
    title: 'Doc3',
    notes: [],
    tags: [],
    content: 'document content 3',
  },
]

describe('DocumentListComponent', () => {
  let component: DocumentListComponent
  let fixture: ComponentFixture<DocumentListComponent>
  let documentListService: DocumentListViewService
  let documentService: DocumentService
  let websocketStatusService: WebsocketStatusService
  let savedViewService: SavedViewService
  let router: Router
  let activatedRoute: ActivatedRoute
  let toastService: ToastService
  let modalService: NgbModal
  let settingsService: SettingsService
  let permissionService: PermissionsService

  beforeEach(async () => {
    TestBed.configureTestingModule({
      imports: [
        RouterTestingModule.withRoutes(routes),
        NgxBootstrapIconsModule.pick(allIcons),
        DocumentListComponent,
      ],
      providers: [
        FilterPipe,
        CustomDatePipe,
        DatePipe,
        DocumentTitlePipe,
        UsernamePipe,
        PermissionsGuard,
        provideHttpClient(withInterceptorsFromDi()),
        provideHttpClientTesting(),
        provideUiTour(),
      ],
    }).compileComponents()

    documentListService = TestBed.inject(DocumentListViewService)
    documentService = TestBed.inject(DocumentService)
    websocketStatusService = TestBed.inject(WebsocketStatusService)
    savedViewService = TestBed.inject(SavedViewService)
    router = TestBed.inject(Router)
    activatedRoute = TestBed.inject(ActivatedRoute)
    toastService = TestBed.inject(ToastService)
    modalService = TestBed.inject(NgbModal)
    settingsService = TestBed.inject(SettingsService)
    permissionService = TestBed.inject(PermissionsService)
    fixture = TestBed.createComponent(DocumentListComponent)
    component = fixture.componentInstance
  })

  it('should reload on new document consumed', () => {
    const reloadSpy = jest.spyOn(documentListService, 'reload')
    const fileStatusSubject = new Subject<FileStatus>()
    jest
      .spyOn(websocketStatusService, 'onDocumentConsumptionFinished')
      .mockReturnValue(fileStatusSubject)
    fixture.detectChanges()
    fileStatusSubject.next(new FileStatus())
    expect(reloadSpy).toHaveBeenCalled()
  })

  it('should reload on document deleted', () => {
    const reloadSpy = jest.spyOn(documentListService, 'reload')
    const documentDeletedSubject = new Subject<boolean>()
    jest
      .spyOn(websocketStatusService, 'onDocumentDeleted')
      .mockReturnValue(documentDeletedSubject)
    fixture.detectChanges()
    documentDeletedSubject.next(true)
    expect(reloadSpy).toHaveBeenCalled()
  })

  it('should show score sort fields on fulltext queries', () => {
    documentListService.setFilterRules([
      {
        rule_type: FILTER_HAS_TAGS_ANY,
        value: '10',
      },
    ])
    fixture.detectChanges()
    expect(component.getSortFields()).toEqual(documentListService.sortFields)

    documentListService.setFilterRules([
      {
        rule_type: FILTER_FULLTEXT_QUERY,
        value: 'foo',
      },
    ])
    fixture.detectChanges()
    expect(component.getSortFields()).toEqual(
      documentListService.sortFieldsFullText
    )
  })

  it('should determine if filtered, support reset', () => {
    fixture.detectChanges()
    documentListService.setFilterRules([
      {
        rule_type: FILTER_HAS_TAGS_ANY,
        value: '10',
      },
    ])
    documentListService.isReloading = false
    fixture.detectChanges()
    expect(component.isFiltered).toBeTruthy()
    expect(fixture.nativeElement.textContent.match(/Reset/g)).toHaveLength(2)
    component.resetFilters()
    fixture.detectChanges()
    expect(fixture.nativeElement.textContent.match(/Reset/g)).toHaveLength(1)
  })

  it('should apply filter rule changes via list service', () => {
    const setFilterRulesSpy = jest.spyOn(documentListService, 'setFilterRules')
    const rules = [{ rule_type: FILTER_HAS_TAGS_ANY, value: '10' }]
    component.onFilterRulesChange(rules)
    expect(setFilterRulesSpy).toHaveBeenCalledWith(rules)
  })

  it('should reset filter rules to page one via list service', () => {
    const setFilterRulesSpy = jest.spyOn(documentListService, 'setFilterRules')
    const rules = [{ rule_type: FILTER_HAS_TAGS_ANY, value: '10' }]
    component.onFilterRulesReset(rules)
    expect(setFilterRulesSpy).toHaveBeenCalledWith(rules, true)
  })

  it('should load saved view from URL', () => {
    const view: SavedView = {
      id: 10,
      sort_field: 'added',
      sort_reverse: true,
      filter_rules: [
        {
          rule_type: FILTER_HAS_TAGS_ANY,
          value: '20',
        },
      ],
    }
    const queryParams = { id: view.id.toString() }
    const getSavedViewSpy = jest.spyOn(savedViewService, 'getCached')
    const setCountSpy = jest.spyOn(savedViewService, 'setDocumentCount')
    jest.spyOn(documentService, 'listFiltered').mockReturnValue(
      of({
        results: docs,
        count: 3,
        all: docs.map((d) => d.id),
      })
    )
    getSavedViewSpy.mockReturnValue(of(view))
    const activateSavedViewSpy = jest.spyOn(
      documentListService,
      'activateSavedViewWithQueryParams'
    )
    activateSavedViewSpy.mockImplementation((view, params) => {})
    jest
      .spyOn(activatedRoute, 'paramMap', 'get')
      .mockReturnValue(of(convertToParamMap(queryParams)))
    activatedRoute.snapshot.queryParams = queryParams
    component.ngOnInit()
    expect(getSavedViewSpy).toHaveBeenCalledWith(view.id)
    expect(activateSavedViewSpy).toHaveBeenCalledWith(
      view,
      convertToParamMap(queryParams)
    )
    expect(setCountSpy).toHaveBeenCalledWith(view, 3)
  })

  it('should 404 on load saved view from URL if no view', () => {
    jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(null)) // e.g. no saved view found
    jest
      .spyOn(activatedRoute, 'paramMap', 'get')
      .mockReturnValue(of(convertToParamMap({ id: '10' })))
    const navigateSpy = jest.spyOn(router, 'navigate')
    fixture.detectChanges()
    expect(navigateSpy).toHaveBeenCalledWith(['404'], { replaceUrl: true })
  })

  it('should load saved view from query params', () => {
    const view: SavedView = {
      id: 10,
      sort_field: 'added',
      sort_reverse: true,
      filter_rules: [
        {
          rule_type: FILTER_HAS_TAGS_ANY,
          value: '20',
        },
      ],
    }
    const getSavedViewSpy = jest.spyOn(savedViewService, 'getCached')
    getSavedViewSpy.mockReturnValue(of(view))
    jest
      .spyOn(activatedRoute, 'queryParamMap', 'get')
      .mockReturnValue(of(convertToParamMap({ view: view.id.toString() })))
    fixture.detectChanges()
    expect(getSavedViewSpy).toHaveBeenCalledWith(view.id)
  })

  it('should update saved view document count on load saved view from query params', () => {
    jest.spyOn(savedViewService, 'getCached').mockReturnValue(
      of({
        id: 10,
        sort_field: 'added',
        sort_reverse: true,
        filter_rules: [],
      })
    )
    jest.spyOn(documentService, 'listFiltered').mockReturnValue(
      of({
        results: docs,
        count: 3,
        all: docs.map((d) => d.id),
      })
    )
    const setCountSpy = jest.spyOn(savedViewService, 'setDocumentCount')
    jest.spyOn(documentService, 'listFiltered').mockReturnValue(
      of({
        results: docs,
        count: 3,
        all: docs.map((d) => d.id),
      })
    )
    component.loadViewConfig(10)
    expect(setCountSpy).toHaveBeenCalledWith(expect.any(Object), 3)
  })

  it('should support 3 different display modes', () => {
    jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
    fixture.detectChanges()
    const displayModeButtons = fixture.debugElement.queryAll(
      By.css('input[type="radio"]')
    )
    expect(component.list.displayMode).toEqual('smallCards')

    displayModeButtons[0].nativeElement.checked = true
    displayModeButtons[0].triggerEventHandler('change')
    fixture.detectChanges()
    expect(component.list.displayMode).toEqual('table')
    expect(fixture.debugElement.queryAll(By.css('tr'))).toHaveLength(4)

    displayModeButtons[1].nativeElement.checked = true
    displayModeButtons[1].triggerEventHandler('change')
    fixture.detectChanges()
    expect(component.list.displayMode).toEqual('smallCards')
    expect(
      fixture.debugElement.queryAll(By.directive(DocumentCardSmallComponent))
    ).toHaveLength(3)

    displayModeButtons[2].nativeElement.checked = true
    displayModeButtons[2].triggerEventHandler('change')
    fixture.detectChanges()
    expect(component.list.displayMode).toEqual('largeCards')
    expect(
      fixture.debugElement.queryAll(By.directive(DocumentCardLargeComponent))
    ).toHaveLength(3)
  })

  it('should support setting sort field', () => {
    expect(documentListService.sortField).toEqual('created')
    fixture.detectChanges()
    const sortDropdown = fixture.debugElement.queryAll(
      By.directive(NgbDropdown)
    )[2]
    const asnSortFieldButton = sortDropdown.query(By.directive(NgbDropdownItem))

    asnSortFieldButton.triggerEventHandler('click')
    fixture.detectChanges()
    expect(documentListService.sortField).toEqual('archive_serial_number')
    documentListService.sortField = 'created'
  })

  it('should support setting sort field by table head', () => {
    component.activeDisplayFields = [DisplayField.ASN]
    jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
    fixture.detectChanges()
    expect(documentListService.sortField).toEqual('created')

    const detailsDisplayModeButton = fixture.debugElement.query(
      By.css('input[type="radio"]')
    )
    detailsDisplayModeButton.nativeElement.checked = true
    detailsDisplayModeButton.triggerEventHandler('change')
    fixture.detectChanges()
    expect(component.list.displayMode).toEqual(DisplayMode.TABLE)

    const sortTh = fixture.debugElement.query(By.directive(SortableDirective))
    sortTh.triggerEventHandler('click')
    fixture.detectChanges()
    expect(documentListService.sortField).toEqual('archive_serial_number')
    documentListService.sortField = 'created'
    expect(documentListService.sortReverse).toBeFalsy()
    component.listSortReverse = true
    expect(documentListService.sortReverse).toBeTruthy()
  })

  it('should support select all, none, page & range', () => {
    jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
    jest
      .spyOn(documentService, 'listAllFilteredIds')
      .mockReturnValue(of(docs.map((d) => d.id)))
    fixture.detectChanges()
    expect(documentListService.selected.size).toEqual(0)
    const docCards = fixture.debugElement.queryAll(
      By.directive(DocumentCardLargeComponent)
    )
    const displayModeButtons = fixture.debugElement.queryAll(
      By.directive(NgbDropdownItem)
    )

    const selectAllSpy = jest.spyOn(documentListService, 'selectAll')
    displayModeButtons[2].triggerEventHandler('click')
    expect(selectAllSpy).toHaveBeenCalled()
    fixture.detectChanges()
    expect(documentListService.selected.size).toEqual(3)
    docCards.forEach((card) => {
      expect(card.context.selected).toBeTruthy()
    })

    const selectNoneSpy = jest.spyOn(documentListService, 'selectNone')
    displayModeButtons[0].triggerEventHandler('click')
    expect(selectNoneSpy).toHaveBeenCalled()
    fixture.detectChanges()
    expect(documentListService.selected.size).toEqual(0)
    docCards.forEach((card) => {
      expect(card.context.selected).toBeFalsy()
    })

    const selectPageSpy = jest.spyOn(documentListService, 'selectPage')
    displayModeButtons[1].triggerEventHandler('click')
    expect(selectPageSpy).toHaveBeenCalled()
    fixture.detectChanges()
    expect(documentListService.selected.size).toEqual(3)
    docCards.forEach((card) => {
      expect(card.context.selected).toBeTruthy()
    })

    component.toggleSelected(docs[0], new MouseEvent('click'))
    fixture.detectChanges()
    expect(documentListService.selected.size).toEqual(2)
    // reset
    displayModeButtons[0].triggerEventHandler('click')
    fixture.detectChanges()
    expect(documentListService.selected.size).toEqual(0)

    // select a range
    component.toggleSelected(docs[0], new MouseEvent('click'))
    component.toggleSelected(
      docs[2],
      new MouseEvent('click', { shiftKey: true })
    )
    fixture.detectChanges()
    expect(documentListService.selected.size).toEqual(3)
  })

  it('should support saving a view', () => {
    const view: SavedView = {
      id: 10,
      name: 'Saved View 10',
      sort_field: 'added',
      sort_reverse: true,
      filter_rules: [
        {
          rule_type: FILTER_HAS_TAGS_ANY,
          value: '20',
        },
      ],
      display_mode: DisplayMode.SMALL_CARDS,
      display_fields: [DisplayField.TITLE],
    }
    jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
    const queryParams = { view: view.id.toString() }
    jest
      .spyOn(activatedRoute, 'queryParamMap', 'get')
      .mockReturnValue(of(convertToParamMap(queryParams)))
    activatedRoute.snapshot.queryParams = queryParams
    router.routerState.snapshot.url = '/view/10/'
    fixture.detectChanges()
    expect(documentListService.activeSavedViewId).toEqual(10)

    const modifiedView = Object.assign({}, view)
    delete modifiedView.name
    const savedViewServicePatch = jest.spyOn(savedViewService, 'patch')
    savedViewServicePatch.mockReturnValue(of(modifiedView))
    const toastSpy = jest.spyOn(toastService, 'showInfo')

    component.saveViewConfig()
    expect(savedViewServicePatch).toHaveBeenCalledWith(modifiedView)
    expect(toastSpy).toHaveBeenCalledWith(
      `View "${view.name}" saved successfully.`
    )
  })

  it('should handle error on view saving', () => {
    const view: SavedView = {
      id: 10,
      name: 'Saved View 10',
      sort_field: 'added',
      sort_reverse: true,
      filter_rules: [
        {
          rule_type: FILTER_HAS_TAGS_ANY,
          value: '20',
        },
      ],
    }
    jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
    const queryParams = { view: view.id.toString() }
    jest
      .spyOn(activatedRoute, 'queryParamMap', 'get')
      .mockReturnValue(of(convertToParamMap(queryParams)))
    activatedRoute.snapshot.queryParams = queryParams
    router.routerState.snapshot.url = '/view/10/'
    fixture.detectChanges()

    const toastErrorSpy = jest.spyOn(toastService, 'showError')
    jest
      .spyOn(savedViewService, 'patch')
      .mockReturnValueOnce(throwError(() => new Error('Error saving view')))
    component.saveViewConfig()
    expect(toastErrorSpy).toHaveBeenCalledWith(
      'Failed to save view "Saved View 10".',
      expect.any(Error)
    )
  })

  it('should not save a view without object change permissions', () => {
    const view: SavedView = {
      id: 10,
      name: 'Saved View 10',
      sort_field: 'added',
      sort_reverse: true,
      filter_rules: [
        {
          rule_type: FILTER_HAS_TAGS_ANY,
          value: '20',
        },
      ],
      owner: 999,
      user_can_change: false,
    }
    jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
    jest
      .spyOn(permissionService, 'currentUserHasObjectPermissions')
      .mockReturnValue(false)
    const queryParams = { view: view.id.toString() }
    jest
      .spyOn(activatedRoute, 'queryParamMap', 'get')
      .mockReturnValue(of(convertToParamMap(queryParams)))
    activatedRoute.snapshot.queryParams = queryParams
    router.routerState.snapshot.url = '/view/10/'
    fixture.detectChanges()

    const patchSpy = jest.spyOn(savedViewService, 'patch')

    component.saveViewConfig()

    expect(patchSpy).not.toHaveBeenCalled()
  })

  it('should support edited view saving as', () => {
    const view: SavedView = {
      id: 10,
      name: 'Saved View 10',
      sort_field: 'added',
      sort_reverse: true,
      filter_rules: [
        {
          rule_type: FILTER_HAS_TAGS_ANY,
          value: '20',
        },
      ],
    }
    jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
    const queryParams = { view: view.id.toString() }
    jest
      .spyOn(activatedRoute, 'queryParamMap', 'get')
      .mockReturnValue(of(convertToParamMap(queryParams)))
    activatedRoute.snapshot.queryParams = queryParams
    router.routerState.snapshot.url = '/view/10/'
    fixture.detectChanges()
    expect(documentListService.activeSavedViewId).toEqual(10)

    const modifiedView = Object.assign({}, view)
    modifiedView.name = 'Foo Bar'

    let openModal: NgbModalRef
    modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
    const modalSpy = jest.spyOn(modalService, 'open')
    const toastSpy = jest.spyOn(toastService, 'showInfo')
    const savedViewServiceCreate = jest.spyOn(savedViewService, 'create')
    const updateVisibilitySpy = jest
      .spyOn(settingsService, 'updateSavedViewsVisibility')
      .mockReturnValue(of({ success: true }))
    savedViewServiceCreate.mockReturnValueOnce(of(modifiedView))
    component.saveViewConfigAs()

    const modalCloseSpy = jest.spyOn(openModal, 'close')
    const permissions = {
      owner: 5,
      set_permissions: {
        view: {
          users: [4],
          groups: [3],
        },
        change: {
          users: [2],
          groups: [1],
        },
      },
    }
    openModal.componentInstance.saveClicked.next({
      name: 'Foo Bar',
      showOnDashboard: true,
      showInSideBar: true,
      permissions_form: permissions,
    })
    expect(savedViewServiceCreate).toHaveBeenCalled()
    expect(savedViewServiceCreate).toHaveBeenCalledWith(
      expect.objectContaining({
        name: 'Foo Bar',
        owner: permissions.owner,
        set_permissions: permissions.set_permissions,
      })
    )
    expect(updateVisibilitySpy).toHaveBeenCalledWith(
      expect.arrayContaining([modifiedView.id]),
      expect.arrayContaining([modifiedView.id])
    )
    expect(modalSpy).toHaveBeenCalled()
    expect(toastSpy).toHaveBeenCalled()
    expect(modalCloseSpy).toHaveBeenCalled()
  })

  it('should handle error on edited view saving as', () => {
    const view: SavedView = {
      id: 10,
      name: 'Saved View 10',
      sort_field: 'added',
      sort_reverse: true,
      filter_rules: [
        {
          rule_type: FILTER_HAS_TAGS_ANY,
          value: '20',
        },
      ],
    }
    jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
    const queryParams = { view: view.id.toString() }
    jest
      .spyOn(activatedRoute, 'queryParamMap', 'get')
      .mockReturnValue(of(convertToParamMap(queryParams)))
    activatedRoute.snapshot.queryParams = queryParams
    router.routerState.snapshot.url = '/view/10/'
    fixture.detectChanges()
    expect(documentListService.activeSavedViewId).toEqual(10)

    const modifiedView = Object.assign({}, view)
    modifiedView.name = 'Foo Bar'

    let openModal: NgbModalRef
    modalService.activeInstances.subscribe((modal) => (openModal = modal[0]))
    const updateVisibilitySpy = jest.spyOn(
      settingsService,
      'updateSavedViewsVisibility'
    )
    jest.spyOn(savedViewService, 'create').mockReturnValueOnce(
      throwError(
        () =>
          new HttpErrorResponse({
            error: { filter_rules: [{ value: '11' }] },
          })
      )
    )
    component.saveViewConfigAs()

    openModal.componentInstance.saveClicked.next({
      name: 'Foo Bar',
      showOnDashboard: true,
      showInSideBar: true,
    })
    expect(updateVisibilitySpy).not.toHaveBeenCalled()
    expect(openModal.componentInstance.error).toEqual({ filter_rules: ['11'] })
  })

  it('should detect saved view changes', () => {
    const view: SavedView = {
      id: 10,
      name: 'Saved View 10',
      sort_field: 'added',
      sort_reverse: true,
      filter_rules: [
        {
          rule_type: FILTER_HAS_TAGS_ANY,
          value: '20',
        },
      ],
      page_size: 5,
      display_mode: DisplayMode.SMALL_CARDS,
      display_fields: [DisplayField.TITLE],
    }
    jest.spyOn(savedViewService, 'getCached').mockReturnValue(of(view))
    const queryParams = { view: view.id.toString() }
    jest
      .spyOn(activatedRoute, 'queryParamMap', 'get')
      .mockReturnValue(of(convertToParamMap(queryParams)))
    activatedRoute.snapshot.queryParams = queryParams
    router.routerState.snapshot.url = '/view/10/'
    fixture.detectChanges()
    expect(documentListService.activeSavedViewId).toEqual(10)

    component.list.displayFields = [DisplayField.ASN]
    expect(component.savedViewIsModified).toBeTruthy()
    component.list.displayFields = [DisplayField.TITLE]
    expect(component.savedViewIsModified).toBeFalsy()
    component.list.displayMode = DisplayMode.TABLE
    expect(component.savedViewIsModified).toBeTruthy()
    component.list.displayMode = DisplayMode.SMALL_CARDS
    expect(component.savedViewIsModified).toBeFalsy()
  })

  it('should navigate to a document', () => {
    fixture.detectChanges()
    const routerSpy = jest.spyOn(router, 'navigate')
    component.openDocumentDetail({ id: 99 })
    expect(routerSpy).toHaveBeenCalledWith(['documents', 99])
  })

  it('should hide columns if no perms or notes disabled', () => {
    jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(true)
    jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
    expect(documentListService.sortField).toEqual('created')

    component.list.displayMode = DisplayMode.TABLE
    component.list.displayFields = DEFAULT_DISPLAY_FIELDS.map((f) => f.id)
    fixture.detectChanges()

    expect(
      fixture.debugElement.queryAll(By.directive(SortableDirective))
    ).toHaveLength(10)

    expect(component.notesEnabled).toBeTruthy()
    settingsService.set(SETTINGS_KEYS.NOTES_ENABLED, false)
    fixture.detectChanges()
    expect(component.notesEnabled).toBeFalsy()
    expect(
      fixture.debugElement.queryAll(By.directive(SortableDirective))
    ).toHaveLength(9)

    // insufficient perms
    jest.spyOn(permissionService, 'currentUserCan').mockReturnValue(false)
    fixture.detectChanges()
    expect(
      fixture.debugElement.queryAll(By.directive(SortableDirective))
    ).toHaveLength(5)
  })

  it('should support toggle on document objects', () => {
    // TODO: this is just for coverage atm
    fixture.detectChanges()
    component.clickTag(1)
    component.clickCorrespondent(2)
    component.clickDocumentType(3)
    component.clickStoragePath(4)
  })

  it('should support quick filter on document more like', () => {
    fixture.detectChanges()
    const qfSpy = jest.spyOn(documentListService, 'quickFilter')
    component.clickMoreLike(99)
    expect(qfSpy).toHaveBeenCalledWith([
      { rule_type: FILTER_FULLTEXT_MORELIKE, value: '99' },
    ])
  })

  it('should support toggling display fields', () => {
    fixture.detectChanges()
    component.activeDisplayFields = [DisplayField.ASN]
    component.toggleDisplayField(DisplayField.TITLE)
    expect(component.activeDisplayFields).toEqual([
      DisplayField.ASN,
      DisplayField.TITLE,
    ])
    component.toggleDisplayField(DisplayField.ASN)
    expect(component.activeDisplayFields).toEqual([DisplayField.TITLE])
  })

  it('should get custom field title', () => {
    fixture.detectChanges()
    jest
      .spyOn(settingsService, 'allDisplayFields', 'get')
      .mockReturnValue([
        { id: 'custom_field_1' as any, name: 'Custom Field 1' },
      ])
    expect(component.getDisplayCustomFieldTitle('custom_field_1')).toEqual(
      'Custom Field 1'
    )
  })

  it('should support hotkeys', () => {
    fixture.detectChanges()
    const resetSpy = jest.spyOn(component['filterEditor'], 'resetSelected')
    jest.spyOn(component, 'isFiltered', 'get').mockReturnValue(true)
    component.clickTag(1)
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'escape' }))
    expect(resetSpy).toHaveBeenCalled()

    jest
      .spyOn(documentListService, 'selected', 'get')
      .mockReturnValue(new Set([1]))
    const clearSelectedSpy = jest.spyOn(documentListService, 'selectNone')
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'escape' }))
    expect(clearSelectedSpy).toHaveBeenCalled()

    const selectAllSpy = jest.spyOn(documentListService, 'selectAll')
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'a' }))
    expect(selectAllSpy).toHaveBeenCalled()

    const selectPageSpy = jest.spyOn(documentListService, 'selectPage')
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'p' }))
    expect(selectPageSpy).toHaveBeenCalled()

    jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
    fixture.detectChanges()
    const detailSpy = jest.spyOn(component, 'openDocumentDetail')
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'o' }))
    expect(detailSpy).toHaveBeenCalledWith(docs[0])

    jest.spyOn(documentListService, 'documents', 'get').mockReturnValue(docs)
    jest
      .spyOn(documentListService, 'selected', 'get')
      .mockReturnValue(new Set([docs[1].id]))
    fixture.detectChanges()
    document.dispatchEvent(new KeyboardEvent('keydown', { key: 'o' }))
    expect(detailSpy).toHaveBeenCalledWith(docs[1].id)

    const lotsOfDocs: Document[] = Array.from({ length: 100 }, (_, i) => ({
      id: i + 1,
      title: `Doc${i + 1}`,
      notes: [],
      content: `document content ${i + 1}`,
    }))
    jest
      .spyOn(documentListService, 'documents', 'get')
      .mockReturnValue(lotsOfDocs)
    jest
      .spyOn(documentService, 'listAllFilteredIds')
      .mockReturnValue(of(lotsOfDocs.map((d) => d.id)))
    jest.spyOn(documentListService, 'getLastPage').mockReturnValue(4)
    fixture.detectChanges()

    expect(component.list.currentPage).toEqual(1)
    document.dispatchEvent(
      new KeyboardEvent('keydown', { key: 'ArrowRight', ctrlKey: true })
    )
    expect(component.list.currentPage).toEqual(2)
    document.dispatchEvent(
      new KeyboardEvent('keydown', { key: 'ArrowLeft', ctrlKey: true })
    )
    expect(component.list.currentPage).toEqual(1)
  })
})
