From 6fb7ab10af398dcc81f032aec4d6baee9bfa9c11 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 17 Dec 2024 15:57:31 +0000 Subject: [PATCH 01/14] Implemet serialization, repo & service, add tests --- .../application/DossiersService.test.ts | 47 +++++++++++ src/dossiers/application/DossiersService.ts | 18 ++++ src/dossiers/domain/DossierRecord.test.ts | 41 +++++++++ src/dossiers/domain/DossierRecord.ts | 52 ++++++++++++ .../infrastructure/DossiersRepository.test.ts | 84 +++++++++++++++++++ .../infrastructure/DossiersRepository.ts | 17 ++++ src/dossiers/ui/DossierDisplay.test.tsx | 0 src/dossiers/ui/DossiersDisplay.tsx | 0 8 files changed, 259 insertions(+) create mode 100644 src/dossiers/application/DossiersService.test.ts create mode 100644 src/dossiers/application/DossiersService.ts create mode 100644 src/dossiers/domain/DossierRecord.test.ts create mode 100644 src/dossiers/domain/DossierRecord.ts create mode 100644 src/dossiers/infrastructure/DossiersRepository.test.ts create mode 100644 src/dossiers/infrastructure/DossiersRepository.ts create mode 100644 src/dossiers/ui/DossierDisplay.test.tsx create mode 100644 src/dossiers/ui/DossiersDisplay.tsx diff --git a/src/dossiers/application/DossiersService.test.ts b/src/dossiers/application/DossiersService.test.ts new file mode 100644 index 000000000..3c22589d4 --- /dev/null +++ b/src/dossiers/application/DossiersService.test.ts @@ -0,0 +1,47 @@ +import { testDelegation, TestData } from 'test-support/utils' +import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' +import DossierRecord from 'dossiers/domain/DossierRecord' +import DossiersService from 'dossiers/application/DossiersService' +import { stringify } from 'query-string' +import { ReferenceType } from 'bibliography/domain/Reference' +import { Provenances } from 'corpus/domain/provenance' +import { PeriodModifiers, Periods } from 'common/period' + +jest.mock('dossiers/infrastructure/DossiersRepository') +const dossiersRepository = new (DossiersRepository as jest.Mock)() + +const dossiersService = new DossiersService(dossiersRepository) + +const resultStub = { + id: 'test', + description: 'some desciption', + isApproximateDate: true, + yearRangeFrom: -500, + yearRangeTo: -470, + relatedKings: [10.2, 11], + provenance: Provenances.Assyria, + script: { + period: Periods['Neo-Assyrian'], + periodModifier: PeriodModifiers.None, + uncertain: false, + }, + references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], +} + +const query = { ids: ['test'] } +const entry = new DossierRecord(resultStub) + +const testData: TestData[] = [ + new TestData( + 'searchByIds', + [stringify(query)], + dossiersRepository.searchByIds, + [entry], + [stringify(query)], + Promise.resolve([entry]) + ), +] + +describe('DossiersService', () => { + testDelegation(dossiersService, testData) +}) diff --git a/src/dossiers/application/DossiersService.ts b/src/dossiers/application/DossiersService.ts new file mode 100644 index 000000000..ac02664d2 --- /dev/null +++ b/src/dossiers/application/DossiersService.ts @@ -0,0 +1,18 @@ +import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' +import DossierRecord from 'dossiers/domain/DossierRecord' + +export interface DossiersSearch { + searchByIds(query: string): Promise +} + +export default class DossiersService implements DossiersSearch { + private readonly dossiersRepository: DossiersRepository + + constructor(afoRegisterRepository: DossiersRepository) { + this.dossiersRepository = afoRegisterRepository + } + + searchByIds(query: string): Promise { + return this.dossiersRepository.searchByIds(query) + } +} diff --git a/src/dossiers/domain/DossierRecord.test.ts b/src/dossiers/domain/DossierRecord.test.ts new file mode 100644 index 000000000..7ac5438da --- /dev/null +++ b/src/dossiers/domain/DossierRecord.test.ts @@ -0,0 +1,41 @@ +import DossierRecord from 'dossiers/domain/DossierRecord' +import { ReferenceType } from 'bibliography/domain/Reference' +import { PeriodModifiers, Periods } from 'common/period' +import { Provenances } from 'corpus/domain/provenance' + +describe('DossierRecord', () => { + const mockRecord = { + id: 'test', + description: 'some desciption', + isApproximateDate: true, + yearRangeFrom: -500, + yearRangeTo: -470, + relatedKings: [10.2, 11], + provenance: Provenances.Assyria, + script: { + period: Periods['Neo-Assyrian'], + periodModifier: PeriodModifiers.None, + uncertain: false, + }, + references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], + } + + describe('constructor', () => { + it('should initialize properties correctly', () => { + const record = new DossierRecord(mockRecord) + expect(record.id).toEqual('test') + expect(record.description).toEqual('some desciption') + expect(record.isApproximateDate).toEqual(true) + expect(record.yearRangeFrom).toEqual(-500) + expect(record.yearRangeTo).toEqual(-470) + expect(record.relatedKings).toEqual([10.2, 11]) + expect(record.provenance).toEqual(Provenances.Assyria) + expect(record.script).toEqual({ + period: Periods['Neo-Assyrian'], + periodModifier: PeriodModifiers.None, + uncertain: false, + }) + expect(record.references).toEqual(['EDITION', 'DISCUSSION']) + }) + }) +}) diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts new file mode 100644 index 000000000..2045e2b00 --- /dev/null +++ b/src/dossiers/domain/DossierRecord.ts @@ -0,0 +1,52 @@ +import { immerable } from 'immer' +import { ReferenceType } from 'bibliography/domain/Reference' +import { Provenance } from 'corpus/domain/provenance' +import { Script } from 'fragmentarium/domain/fragment' + +interface DossierRecordData { + readonly id: string + readonly description?: string + readonly isApproximateDate?: boolean + readonly yearRangeFrom?: number + readonly yearRangeTo?: number + readonly relatedKings?: number[] + readonly provenance?: Provenance + readonly script?: Script + readonly references?: ReferenceType[] +} + +export default class DossierRecord { + [immerable] = true + + readonly id: string + readonly description?: string + readonly isApproximateDate: boolean + readonly yearRangeFrom?: number + readonly yearRangeTo?: number + readonly relatedKings: number[] + readonly provenance?: Provenance + readonly script?: Script + readonly references: ReferenceType[] + + constructor({ + id, + description, + isApproximateDate = false, + yearRangeFrom, + yearRangeTo, + relatedKings = [], + provenance, + script, + references = [], + }: DossierRecordData) { + this.id = id + this.description = description + this.isApproximateDate = isApproximateDate + this.yearRangeFrom = yearRangeFrom + this.yearRangeTo = yearRangeTo + this.relatedKings = relatedKings + this.provenance = provenance + this.script = script + this.references = references + } +} diff --git a/src/dossiers/infrastructure/DossiersRepository.test.ts b/src/dossiers/infrastructure/DossiersRepository.test.ts new file mode 100644 index 000000000..e68498838 --- /dev/null +++ b/src/dossiers/infrastructure/DossiersRepository.test.ts @@ -0,0 +1,84 @@ +import { testDelegation, TestData } from 'test-support/utils' +import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' +import DossierRecord from 'dossiers/domain/DossierRecord' +import { stringify } from 'query-string' +import ApiClient from 'http/ApiClient' +import { PeriodModifiers, Periods } from 'common/period' +import { ReferenceType } from 'bibliography/domain/Reference' +import { Provenances } from 'corpus/domain/provenance' + +jest.mock('http/ApiClient') +jest.mock('dossiers/application/DossiersService') + +const apiClient = new (ApiClient as jest.Mock>)() +const dossiersRepository = new DossiersRepository(apiClient) + +const resultStub = { + id: 'test', + description: 'some description', + isApproximateDate: true, + yearRangeFrom: -500, + yearRangeTo: -470, + relatedKings: [10.2, 11], + provenance: Provenances.Assyria, + script: { + period: Periods['Neo-Assyrian'], + periodModifier: PeriodModifiers.None, + uncertain: false, + }, + references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], +} + +const query = { ids: ['test'] } +const record = new DossierRecord(resultStub) + +const testData: TestData[] = [ + new TestData( + 'searchByIds', + [stringify(query)], + apiClient.fetchJson, + [record], + [`/dossiers?${stringify(query)}`, false], + Promise.resolve([resultStub]) + ), +] + +describe('dossiersService', () => testDelegation(dossiersRepository, testData)) + +describe('DossiersRepository - search by ids', () => { + it('handles search without errors', async () => { + apiClient.fetchJson.mockResolvedValueOnce([resultStub]) + const response = await dossiersRepository.searchByIds(stringify(query)) + expect(response).toEqual([record]) + expect(apiClient.fetchJson).toHaveBeenCalledWith( + `/dossiers?${stringify(query)}`, + false + ) + }) + + it('handles different query strings', async () => { + const query2 = { id: 'test2' } + const resultStub2 = { + ...resultStub, + id: 'test2', + description: 'another description', + } + const record2 = new DossierRecord(resultStub2) + apiClient.fetchJson.mockResolvedValueOnce([resultStub, resultStub2]) + const response = await dossiersRepository.searchByIds(stringify(query2)) + expect(response).toEqual([record, record2]) + }) + + it('handles empty response', async () => { + apiClient.fetchJson.mockResolvedValueOnce([]) + const response = await dossiersRepository.searchByIds(stringify(query)) + expect(response).toEqual([]) + }) + + it('handles API errors', async () => { + apiClient.fetchJson.mockRejectedValueOnce(new Error('API Error')) + await expect( + dossiersRepository.searchByIds(stringify(query)) + ).rejects.toThrow('API Error') + }) +}) diff --git a/src/dossiers/infrastructure/DossiersRepository.ts b/src/dossiers/infrastructure/DossiersRepository.ts new file mode 100644 index 000000000..d77021c76 --- /dev/null +++ b/src/dossiers/infrastructure/DossiersRepository.ts @@ -0,0 +1,17 @@ +import DossierRecord from 'dossiers/domain/DossierRecord' +import Promise from 'bluebird' +import ApiClient from 'http/ApiClient' + +export default class DossiersRepository { + private readonly apiClient: ApiClient + + constructor(apiClient: ApiClient) { + this.apiClient = apiClient + } + + searchByIds(query: string): Promise { + return this.apiClient + .fetchJson(`/dossiers?${query}`, false) + .then((result) => result.map((data) => new DossierRecord(data))) + } +} diff --git a/src/dossiers/ui/DossierDisplay.test.tsx b/src/dossiers/ui/DossierDisplay.test.tsx new file mode 100644 index 000000000..e69de29bb diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx new file mode 100644 index 000000000..e69de29bb From 6d56e1568e48dad31ed538695d3101c86c2587ac Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Wed, 18 Dec 2024 18:04:29 +0000 Subject: [PATCH 02/14] Implement display & update (WiP) --- .../application/DossiersService.test.ts | 4 +- src/dossiers/application/DossiersService.ts | 6 +- src/dossiers/domain/DossierRecord.ts | 42 +++++++++++ .../infrastructure/DossiersRepository.test.ts | 22 +++--- .../infrastructure/DossiersRepository.ts | 5 +- src/dossiers/ui/DossiersDisplay.tsx | 69 +++++++++++++++++++ src/fragmentarium/domain/fragment.ts | 3 + src/fragmentarium/ui/info/Details.tsx | 4 ++ 8 files changed, 137 insertions(+), 18 deletions(-) diff --git a/src/dossiers/application/DossiersService.test.ts b/src/dossiers/application/DossiersService.test.ts index 3c22589d4..002304fe4 100644 --- a/src/dossiers/application/DossiersService.test.ts +++ b/src/dossiers/application/DossiersService.test.ts @@ -33,9 +33,9 @@ const entry = new DossierRecord(resultStub) const testData: TestData[] = [ new TestData( - 'searchByIds', + 'queryByIds', [stringify(query)], - dossiersRepository.searchByIds, + dossiersRepository.queryByIds, [entry], [stringify(query)], Promise.resolve([entry]) diff --git a/src/dossiers/application/DossiersService.ts b/src/dossiers/application/DossiersService.ts index ac02664d2..7a543c6a1 100644 --- a/src/dossiers/application/DossiersService.ts +++ b/src/dossiers/application/DossiersService.ts @@ -2,7 +2,7 @@ import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' import DossierRecord from 'dossiers/domain/DossierRecord' export interface DossiersSearch { - searchByIds(query: string): Promise + queryByIds(query: string[]): Promise } export default class DossiersService implements DossiersSearch { @@ -12,7 +12,7 @@ export default class DossiersService implements DossiersSearch { this.dossiersRepository = afoRegisterRepository } - searchByIds(query: string): Promise { - return this.dossiersRepository.searchByIds(query) + queryByIds(query: string[]): Promise { + return this.dossiersRepository.queryByIds(query) } } diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts index 2045e2b00..31ba8d2f9 100644 --- a/src/dossiers/domain/DossierRecord.ts +++ b/src/dossiers/domain/DossierRecord.ts @@ -49,4 +49,46 @@ export default class DossierRecord { this.script = script this.references = references } + + toMarkdownString(): string { + return `${this.YearsToMarkdownString()}` + } + + private YearsToMarkdownString(): string { + const yearRangeFrom = this.formatYear(this.yearRangeFrom) + const yearRangeTo = this.formatYear(this.yearRangeTo) + + if (!yearRangeFrom) { + return '' + } + + if (this.isApproximateDate) { + return this.formatApproximateRange(yearRangeFrom, yearRangeTo) + } + + return this.formatExactRange(yearRangeFrom, yearRangeTo) + } + + private formatYear(year: number | undefined): string { + if (year === undefined) return '' + const prefix = year < 0 ? 'BCE' : 'CE' + return `${Math.abs(year)} ${prefix}` + } + + private formatApproximateRange( + yearRangeFrom: string, + yearRangeTo: string + ): string { + if (yearRangeFrom === yearRangeTo || !yearRangeTo) { + return `ca. ${yearRangeFrom}` + } + return `ca. ${yearRangeFrom} - ${yearRangeTo}` + } + + private formatExactRange(yearRangeFrom: string, yearRangeTo: string): string { + if (yearRangeFrom === yearRangeTo || !yearRangeTo) { + return yearRangeFrom + } + return `${yearRangeFrom} - ${yearRangeTo}` + } } diff --git a/src/dossiers/infrastructure/DossiersRepository.test.ts b/src/dossiers/infrastructure/DossiersRepository.test.ts index e68498838..1943e5171 100644 --- a/src/dossiers/infrastructure/DossiersRepository.test.ts +++ b/src/dossiers/infrastructure/DossiersRepository.test.ts @@ -29,16 +29,16 @@ const resultStub = { references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], } -const query = { ids: ['test'] } +const query = ['test'] const record = new DossierRecord(resultStub) const testData: TestData[] = [ new TestData( - 'searchByIds', + 'queryByIds', [stringify(query)], apiClient.fetchJson, [record], - [`/dossiers?${stringify(query)}`, false], + [`/dossiers?${stringify({ ids: query })}`, false], Promise.resolve([resultStub]) ), ] @@ -48,16 +48,16 @@ describe('dossiersService', () => testDelegation(dossiersRepository, testData)) describe('DossiersRepository - search by ids', () => { it('handles search without errors', async () => { apiClient.fetchJson.mockResolvedValueOnce([resultStub]) - const response = await dossiersRepository.searchByIds(stringify(query)) + const response = dossiersRepository.queryByIds(query) expect(response).toEqual([record]) expect(apiClient.fetchJson).toHaveBeenCalledWith( - `/dossiers?${stringify(query)}`, + `/dossiers?${stringify({ ids: query })}`, false ) }) it('handles different query strings', async () => { - const query2 = { id: 'test2' } + const query2 = ['test2'] const resultStub2 = { ...resultStub, id: 'test2', @@ -65,20 +65,20 @@ describe('DossiersRepository - search by ids', () => { } const record2 = new DossierRecord(resultStub2) apiClient.fetchJson.mockResolvedValueOnce([resultStub, resultStub2]) - const response = await dossiersRepository.searchByIds(stringify(query2)) + const response = dossiersRepository.queryByIds(query2) expect(response).toEqual([record, record2]) }) it('handles empty response', async () => { apiClient.fetchJson.mockResolvedValueOnce([]) - const response = await dossiersRepository.searchByIds(stringify(query)) + const response = dossiersRepository.queryByIds(query) expect(response).toEqual([]) }) it('handles API errors', async () => { apiClient.fetchJson.mockRejectedValueOnce(new Error('API Error')) - await expect( - dossiersRepository.searchByIds(stringify(query)) - ).rejects.toThrow('API Error') + await expect(dossiersRepository.queryByIds(query)).rejects.toThrow( + 'API Error' + ) }) }) diff --git a/src/dossiers/infrastructure/DossiersRepository.ts b/src/dossiers/infrastructure/DossiersRepository.ts index d77021c76..735dc3940 100644 --- a/src/dossiers/infrastructure/DossiersRepository.ts +++ b/src/dossiers/infrastructure/DossiersRepository.ts @@ -1,6 +1,7 @@ import DossierRecord from 'dossiers/domain/DossierRecord' import Promise from 'bluebird' import ApiClient from 'http/ApiClient' +import { stringify } from 'query-string' export default class DossiersRepository { private readonly apiClient: ApiClient @@ -9,9 +10,9 @@ export default class DossiersRepository { this.apiClient = apiClient } - searchByIds(query: string): Promise { + queryByIds(query: string[]): Promise { return this.apiClient - .fetchJson(`/dossiers?${query}`, false) + .fetchJson(`/dossiers?${stringify({ ids: query })}`, false) .then((result) => result.map((data) => new DossierRecord(data))) } } diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index e69de29bb..88cf9a77d 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -0,0 +1,69 @@ +import React from 'react' +import DossierRecord from 'dossiers/domain/DossierRecord' +import MarkdownAndHtmlToHtml from 'common/MarkdownAndHtmlToHtml' +import { Fragment } from 'fragmentarium/domain/fragment' +import withData from 'http/withData' +import DossiersService from 'dossiers/application/DossiersService' +import _ from 'lodash' +import Bluebird from 'bluebird' + +export function DossierRecordDisplay({ + record, + index, +}: { + record: DossierRecord + index: string | number +}): JSX.Element { + return ( + + ) +} + +export function DossierRecordsListDisplay({ + data, + ...props +}: { + data: { records: readonly DossierRecord[] } +} & React.OlHTMLAttributes): JSX.Element { + const { records } = data + + if (records.length < 1) { + return <> + } + return ( +
    + {records.map((record, index) => ( +
  1. + +
  2. + ))} +
+ ) +} + +export default withData< + unknown, + { + dossiersService: DossiersService + fragment: Fragment + }, + { records: readonly DossierRecord[] } +>( + DossierRecordsListDisplay, + (props) => { + return Bluebird.resolve( + props.dossiersService + .queryByIds(props.fragment.dossiers.map((dossier) => dossier)) + .then((records) => ({ records })) + ) + }, + { + watch: (props) => [...props.fragment.dossiers], + filter: (props) => !_.isEmpty(props.fragment.dossiers), + defaultData: () => ({ records: [] }), + } +) diff --git a/src/fragmentarium/domain/fragment.ts b/src/fragmentarium/domain/fragment.ts index 645b4065b..8c036d74b 100644 --- a/src/fragmentarium/domain/fragment.ts +++ b/src/fragmentarium/domain/fragment.ts @@ -106,6 +106,7 @@ interface FragmentProps { archaeology?: Archaeology colophon?: Colophon authorizedScopes?: string[] + dossiers: readonly string[] } export class Fragment { @@ -135,6 +136,7 @@ export class Fragment { readonly script: Script, readonly externalNumbers: ExternalNumbers, readonly projects: ReadonlyArray, + readonly dossiers: readonly string[], readonly date?: MesopotamianDate, readonly datesInText?: ReadonlyArray, readonly archaeology?: Archaeology, @@ -167,6 +169,7 @@ export class Fragment { props.script, props.externalNumbers, props.projects, + props.dossiers, props.date, props.datesInText, props.archaeology, diff --git a/src/fragmentarium/ui/info/Details.tsx b/src/fragmentarium/ui/info/Details.tsx index 789eec8cc..8997ce8a6 100644 --- a/src/fragmentarium/ui/info/Details.tsx +++ b/src/fragmentarium/ui/info/Details.tsx @@ -13,6 +13,7 @@ import Bluebird from 'bluebird' import { MesopotamianDate } from 'chronology/domain/Date' import DatesInTextSelection from 'chronology/ui/DateEditor/DatesInTextSelection' import { DateRange, PartialDate } from 'fragmentarium/domain/archaeology' +import { FragmentDossierRecordsDisplay } from 'dossiers/ui/DossiersDisplay' interface Props { readonly fragment: Fragment @@ -194,6 +195,9 @@ function Details({ )}
  • {`Findspot: ${findspotString || '-'}`}
  • +
  • + +
  • Date: Thu, 19 Dec 2024 19:21:18 +0000 Subject: [PATCH 03/14] Update infrastructure (WiP) --- .../infrastructure/DossiersRepository.test.ts | 21 ++-- .../infrastructure/DossiersRepository.ts | 3 +- src/dossiers/ui/DossierDisplay.test.tsx | 105 ++++++++++++++++++ src/dossiers/ui/DossiersDisplay.tsx | 5 +- src/fragmentarium/domain/FragmentDtos.ts | 1 + src/test-support/fragment-fixtures.ts | 1 + src/test-support/test-fragment.ts | 2 + 7 files changed, 127 insertions(+), 11 deletions(-) diff --git a/src/dossiers/infrastructure/DossiersRepository.test.ts b/src/dossiers/infrastructure/DossiersRepository.test.ts index 1943e5171..a0eb01538 100644 --- a/src/dossiers/infrastructure/DossiersRepository.test.ts +++ b/src/dossiers/infrastructure/DossiersRepository.test.ts @@ -29,7 +29,7 @@ const resultStub = { references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], } -const query = ['test'] +const query = ['test', 'test2'] const record = new DossierRecord(resultStub) const testData: TestData[] = [ @@ -38,7 +38,7 @@ const testData: TestData[] = [ [stringify(query)], apiClient.fetchJson, [record], - [`/dossiers?${stringify({ ids: query })}`, false], + ['/dossiers?ids=0%3Dtest%261%3Dtest2', false], Promise.resolve([resultStub]) ), ] @@ -46,12 +46,15 @@ const testData: TestData[] = [ describe('dossiersService', () => testDelegation(dossiersRepository, testData)) describe('DossiersRepository - search by ids', () => { - it('handles search without errors', async () => { + it('handles search without errors', () => { apiClient.fetchJson.mockResolvedValueOnce([resultStub]) const response = dossiersRepository.queryByIds(query) - expect(response).toEqual([record]) + response.then((resolvedResponse) => { + expect(resolvedResponse).toEqual([record]) + }) + expect(apiClient.fetchJson).toHaveBeenCalledWith( - `/dossiers?${stringify({ ids: query })}`, + '/dossiers?ids=test&ids=test2', false ) }) @@ -66,13 +69,17 @@ describe('DossiersRepository - search by ids', () => { const record2 = new DossierRecord(resultStub2) apiClient.fetchJson.mockResolvedValueOnce([resultStub, resultStub2]) const response = dossiersRepository.queryByIds(query2) - expect(response).toEqual([record, record2]) + response.then((resolvedResponse) => { + expect(resolvedResponse).toEqual([record, record2]) + }) }) it('handles empty response', async () => { apiClient.fetchJson.mockResolvedValueOnce([]) const response = dossiersRepository.queryByIds(query) - expect(response).toEqual([]) + response.then((resolvedResponse) => { + expect(resolvedResponse).toEqual([]) + }) }) it('handles API errors', async () => { diff --git a/src/dossiers/infrastructure/DossiersRepository.ts b/src/dossiers/infrastructure/DossiersRepository.ts index 735dc3940..52d99abb6 100644 --- a/src/dossiers/infrastructure/DossiersRepository.ts +++ b/src/dossiers/infrastructure/DossiersRepository.ts @@ -11,8 +11,9 @@ export default class DossiersRepository { } queryByIds(query: string[]): Promise { + const queryString = stringify({ ids: query }) return this.apiClient - .fetchJson(`/dossiers?${stringify({ ids: query })}`, false) + .fetchJson(`/dossiers?${queryString}`, false) .then((result) => result.map((data) => new DossierRecord(data))) } } diff --git a/src/dossiers/ui/DossierDisplay.test.tsx b/src/dossiers/ui/DossierDisplay.test.tsx index e69de29bb..858f2daf3 100644 --- a/src/dossiers/ui/DossierDisplay.test.tsx +++ b/src/dossiers/ui/DossierDisplay.test.tsx @@ -0,0 +1,105 @@ +import React from 'react' +import { render, screen } from '@testing-library/react' +import '@testing-library/jest-dom' +import DossierRecord from 'dossiers/domain/DossierRecord' +import DossiersService from 'dossiers/application/DossiersService' +import { Fragment } from 'fragmentarium/domain/fragment' +import { + DossierRecordDisplay, + DossierRecordsListDisplay, +} from './DossiersDisplay' +import Bluebird from 'bluebird' +import withData from 'http/withData' +import { Provenances } from 'corpus/domain/provenance' +import { PeriodModifiers, Periods } from 'common/period' +import { fragmentFactory } from 'test-support/fragment-fixtures' + +jest.mock('common/MarkdownAndHtmlToHtml', () => ({ + __esModule: true, + default: ({ markdownAndHtml }: { markdownAndHtml: string }) => ( +
    {markdownAndHtml}
    + ), +})) + +const mockRecord = new DossierRecord({ + id: 'test', + description: 'Test description', + isApproximateDate: true, + yearRangeFrom: -500, + yearRangeTo: -470, + relatedKings: [10.2, 11], + provenance: Provenances['Assyria'], + script: { + period: Periods['Neo-Assyrian'], + periodModifier: PeriodModifiers.None, + uncertain: false, + }, + references: ['EDITION', 'DISCUSSION'], +}) + +describe('DossierRecordDisplay', () => { + it('renders correctly with a record', () => { + render() + expect(screen.getByText(mockRecord.toMarkdownString())).toBeInTheDocument() + }) +}) + +describe('DossierRecordsListDisplay', () => { + it('renders an empty component when no records are present', () => { + render() + expect(screen.queryByRole('list')).not.toBeInTheDocument() + }) + + it('renders a list of records', () => { + const records = [ + mockRecord, + new DossierRecord({ ...mockRecord, id: 'test2' }), + ] + render() + + expect(screen.getAllByRole('listitem')).toHaveLength(records.length) + expect(screen.getAllByText('ca. 500 BCE - 470 BCE')).toHaveLength( + records.length + ) + }) +}) + +describe('withData HOC integration', () => { + it('fetches data and passes it to the wrapped component', async () => { + const mockDossiersService = { + queryByIds: jest.fn().mockResolvedValueOnce([mockRecord]), + } + const mockFragment = fragmentFactory.build({ + dossiers: ['test'], + }) + + const WrappedComponent = withData< + unknown, + { dossiersService: DossiersService; fragment: Fragment }, + { records: readonly DossierRecord[] } + >( + DossierRecordsListDisplay, + (props) => + Bluebird.resolve( + props.dossiersService + .queryByIds([...props.fragment.dossiers]) + .then((records) => ({ records })) + ), + { + watch: (props) => [...props.fragment.dossiers], + filter: (props) => !!props.fragment.dossiers.length, + defaultData: () => ({ records: [] }), + } + ) + + render( + + ) + + await screen.findByText(mockRecord.toMarkdownString()) + expect(mockDossiersService.queryByIds).toHaveBeenCalledWith(['test']) + }) +}) diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index 88cf9a77d..0e6741ab4 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -25,7 +25,6 @@ export function DossierRecordDisplay({ export function DossierRecordsListDisplay({ data, - ...props }: { data: { records: readonly DossierRecord[] } } & React.OlHTMLAttributes): JSX.Element { @@ -35,7 +34,7 @@ export function DossierRecordsListDisplay({ return <> } return ( -
      +
        {records.map((record, index) => (
      1. @@ -57,7 +56,7 @@ export default withData< (props) => { return Bluebird.resolve( props.dossiersService - .queryByIds(props.fragment.dossiers.map((dossier) => dossier)) + .queryByIds([...props.fragment.dossiers]) .then((records) => ({ records })) ) }, diff --git a/src/fragmentarium/domain/FragmentDtos.ts b/src/fragmentarium/domain/FragmentDtos.ts index 16600e15a..047c334dc 100644 --- a/src/fragmentarium/domain/FragmentDtos.ts +++ b/src/fragmentarium/domain/FragmentDtos.ts @@ -116,6 +116,7 @@ export default interface FragmentDto { script: ScriptDto externalNumbers: ExternalNumbers projects: readonly string[] + dossiers: readonly string[] date?: MesopotamianDateDto datesInText?: readonly MesopotamianDateDto[] archaeology?: Omit & { diff --git a/src/test-support/fragment-fixtures.ts b/src/test-support/fragment-fixtures.ts index 7ebc67c0b..2b297af12 100644 --- a/src/test-support/fragment-fixtures.ts +++ b/src/test-support/fragment-fixtures.ts @@ -86,6 +86,7 @@ export const fragmentFactory = Factory.define( externalNumbersFactory.build({}, { transient: { chance } }), associations.projects ?? [], + associations.dossiers ?? [], associations.date ?? new MesopotamianDate({ year: { value: '1' }, diff --git a/src/test-support/test-fragment.ts b/src/test-support/test-fragment.ts index aa4096d2b..556c60daf 100644 --- a/src/test-support/test-fragment.ts +++ b/src/test-support/test-fragment.ts @@ -415,6 +415,7 @@ export const fragmentDto: FragmentDto = { }, externalNumbers, projects: [], + dossiers: [], date: { year: { value: '1' }, month: { value: '1' }, @@ -518,6 +519,7 @@ export const fragment = new Fragment( }, externalNumbers, [], + [], new MesopotamianDate({ year: { value: '1' }, month: { value: '1' }, From 6fa70358906e75408a6aa79ed61693ab30aaa061 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Mon, 23 Dec 2024 22:52:08 +0000 Subject: [PATCH 04/14] Update (WiP) --- src/dossiers/domain/DossierReference.ts | 4 +++ .../infrastructure/DossiersRepository.ts | 2 +- src/dossiers/ui/DossierDisplay.test.tsx | 27 +++---------------- src/dossiers/ui/DossiersDisplay.tsx | 8 ++++-- src/fragmentarium/domain/FragmentDtos.ts | 3 ++- src/fragmentarium/domain/fragment.ts | 5 ++-- .../ui/fragment/CuneiformFragment.tsx | 7 +++++ .../ui/fragment/FragmentView.tsx | 4 +++ src/fragmentarium/ui/info/Details.test.tsx | 6 +++++ src/fragmentarium/ui/info/Details.tsx | 10 +++++-- src/fragmentarium/ui/info/Info.tsx | 4 +++ src/index.tsx | 5 ++++ src/router/fragmentariumRoutes.tsx | 4 +++ src/router/router.tsx | 2 ++ src/test-support/AppDriver.tsx | 6 +++++ 15 files changed, 65 insertions(+), 32 deletions(-) create mode 100644 src/dossiers/domain/DossierReference.ts diff --git a/src/dossiers/domain/DossierReference.ts b/src/dossiers/domain/DossierReference.ts new file mode 100644 index 000000000..1c677dd90 --- /dev/null +++ b/src/dossiers/domain/DossierReference.ts @@ -0,0 +1,4 @@ +export interface DossierReference { + readonly dossierId: string + readonly isUncertain?: boolean +} diff --git a/src/dossiers/infrastructure/DossiersRepository.ts b/src/dossiers/infrastructure/DossiersRepository.ts index 52d99abb6..8fa947430 100644 --- a/src/dossiers/infrastructure/DossiersRepository.ts +++ b/src/dossiers/infrastructure/DossiersRepository.ts @@ -11,7 +11,7 @@ export default class DossiersRepository { } queryByIds(query: string[]): Promise { - const queryString = stringify({ ids: query }) + const queryString = stringify({ ids: query }, { arrayFormat: 'index' }) return this.apiClient .fetchJson(`/dossiers?${queryString}`, false) .then((result) => result.map((data) => new DossierRecord(data))) diff --git a/src/dossiers/ui/DossierDisplay.test.tsx b/src/dossiers/ui/DossierDisplay.test.tsx index 858f2daf3..c3e9b1243 100644 --- a/src/dossiers/ui/DossierDisplay.test.tsx +++ b/src/dossiers/ui/DossierDisplay.test.tsx @@ -4,12 +4,10 @@ import '@testing-library/jest-dom' import DossierRecord from 'dossiers/domain/DossierRecord' import DossiersService from 'dossiers/application/DossiersService' import { Fragment } from 'fragmentarium/domain/fragment' -import { +import FragmentDossierRecordsDisplay, { DossierRecordDisplay, DossierRecordsListDisplay, } from './DossiersDisplay' -import Bluebird from 'bluebird' -import withData from 'http/withData' import { Provenances } from 'corpus/domain/provenance' import { PeriodModifiers, Periods } from 'common/period' import { fragmentFactory } from 'test-support/fragment-fixtures' @@ -70,30 +68,11 @@ describe('withData HOC integration', () => { queryByIds: jest.fn().mockResolvedValueOnce([mockRecord]), } const mockFragment = fragmentFactory.build({ - dossiers: ['test'], + dossiers: [{ dossierId: 'test', isUncertain: true }], }) - const WrappedComponent = withData< - unknown, - { dossiersService: DossiersService; fragment: Fragment }, - { records: readonly DossierRecord[] } - >( - DossierRecordsListDisplay, - (props) => - Bluebird.resolve( - props.dossiersService - .queryByIds([...props.fragment.dossiers]) - .then((records) => ({ records })) - ), - { - watch: (props) => [...props.fragment.dossiers], - filter: (props) => !!props.fragment.dossiers.length, - defaultData: () => ({ records: [] }), - } - ) - render( - diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index 0e6741ab4..76a9758eb 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -44,7 +44,7 @@ export function DossierRecordsListDisplay({ ) } -export default withData< +const FragmentDossierRecordsDisplay = withData< unknown, { dossiersService: DossiersService @@ -56,7 +56,9 @@ export default withData< (props) => { return Bluebird.resolve( props.dossiersService - .queryByIds([...props.fragment.dossiers]) + .queryByIds([ + ...props.fragment.dossiers.map((record) => record.dossierId), + ]) .then((records) => ({ records })) ) }, @@ -66,3 +68,5 @@ export default withData< defaultData: () => ({ records: [] }), } ) + +export default FragmentDossierRecordsDisplay diff --git a/src/fragmentarium/domain/FragmentDtos.ts b/src/fragmentarium/domain/FragmentDtos.ts index 047c334dc..dbffa8065 100644 --- a/src/fragmentarium/domain/FragmentDtos.ts +++ b/src/fragmentarium/domain/FragmentDtos.ts @@ -11,6 +11,7 @@ import { import { ArchaeologyDto } from './archaeologyDtos' import { MuseumKey } from './museum' import { ColophonDto } from 'fragmentarium/domain/Colophon' +import { DossierReference } from 'dossiers/domain/DossierReference' interface MeasureDto { value?: number @@ -116,7 +117,7 @@ export default interface FragmentDto { script: ScriptDto externalNumbers: ExternalNumbers projects: readonly string[] - dossiers: readonly string[] + dossiers: readonly DossierReference[] date?: MesopotamianDateDto datesInText?: readonly MesopotamianDateDto[] archaeology?: Omit & { diff --git a/src/fragmentarium/domain/fragment.ts b/src/fragmentarium/domain/fragment.ts index 8c036d74b..52e0f166f 100644 --- a/src/fragmentarium/domain/fragment.ts +++ b/src/fragmentarium/domain/fragment.ts @@ -20,6 +20,7 @@ import { ResearchProject } from 'research-projects/researchProject' import { MesopotamianDate } from 'chronology/domain/Date' import { Archaeology } from './archaeology' import { Colophon } from 'fragmentarium/domain/Colophon' +import { DossierReference } from 'dossiers/domain/DossierReference' export interface FragmentInfo { readonly number: string @@ -106,7 +107,7 @@ interface FragmentProps { archaeology?: Archaeology colophon?: Colophon authorizedScopes?: string[] - dossiers: readonly string[] + dossiers: ReadonlyArray } export class Fragment { @@ -136,7 +137,7 @@ export class Fragment { readonly script: Script, readonly externalNumbers: ExternalNumbers, readonly projects: ReadonlyArray, - readonly dossiers: readonly string[], + readonly dossiers: ReadonlyArray, readonly date?: MesopotamianDate, readonly datesInText?: ReadonlyArray, readonly archaeology?: Archaeology, diff --git a/src/fragmentarium/ui/fragment/CuneiformFragment.tsx b/src/fragmentarium/ui/fragment/CuneiformFragment.tsx index aa6625ce9..cc60d9a78 100644 --- a/src/fragmentarium/ui/fragment/CuneiformFragment.tsx +++ b/src/fragmentarium/ui/fragment/CuneiformFragment.tsx @@ -16,11 +16,13 @@ import ErrorBoundary from 'common/ErrorBoundary' import { FindspotService } from 'fragmentarium/application/FindspotService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' import { EditorTabs } from 'fragmentarium/ui/fragment/CuneiformFragmentEditor' +import DossiersService from 'dossiers/application/DossiersService' type CuneiformFragmentProps = { fragment: Fragment fragmentService: FragmentService fragmentSearchService: FragmentSearchService + dossiersService: DossiersService afoRegisterService: AfoRegisterService wordService: WordService findspotService: FindspotService @@ -35,6 +37,7 @@ const CuneiformFragment: FunctionComponent = ({ fragment, fragmentService, fragmentSearchService, + dossiersService, afoRegisterService, wordService, findspotService, @@ -53,6 +56,7 @@ const CuneiformFragment: FunctionComponent = ({ @@ -97,6 +101,7 @@ type ControllerProps = { fragment: Fragment fragmentService: FragmentService fragmentSearchService: FragmentSearchService + dossiersService: DossiersService afoRegisterService: AfoRegisterService wordService: WordService findspotService: FindspotService @@ -108,6 +113,7 @@ const CuneiformFragmentController: FunctionComponent = ({ fragment, fragmentService, fragmentSearchService, + dossiersService, afoRegisterService, wordService, findspotService, @@ -145,6 +151,7 @@ const CuneiformFragmentController: FunctionComponent = ({ fragment={currentFragment} fragmentService={fragmentService} fragmentSearchService={fragmentSearchService} + dossiersService={dossiersService} afoRegisterService={afoRegisterService} wordService={wordService} findspotService={findspotService} diff --git a/src/fragmentarium/ui/fragment/FragmentView.tsx b/src/fragmentarium/ui/fragment/FragmentView.tsx index 27bac7a42..6a6df868f 100644 --- a/src/fragmentarium/ui/fragment/FragmentView.tsx +++ b/src/fragmentarium/ui/fragment/FragmentView.tsx @@ -19,6 +19,7 @@ import { Session } from 'auth/Session' import { HeadTags } from 'router/head' import { FindspotService } from 'fragmentarium/application/FindspotService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' +import DossiersService from 'dossiers/application/DossiersService' function TagSignsButton({ number, @@ -40,6 +41,7 @@ type Props = { fragment: Fragment fragmentService: FragmentService fragmentSearchService: FragmentSearchService + dossiersService: DossiersService wordService: WordService findspotService: FindspotService afoRegisterService: AfoRegisterService @@ -67,6 +69,7 @@ function FragmentView({ fragment, fragmentService, fragmentSearchService, + dossiersService, afoRegisterService, wordService, findspotService, @@ -109,6 +112,7 @@ function FragmentView({ fragment={fragment} fragmentService={fragmentService} fragmentSearchService={fragmentSearchService} + dossiersService={dossiersService} wordService={wordService} findspotService={findspotService} activeFolio={activeFolio} diff --git a/src/fragmentarium/ui/info/Details.test.tsx b/src/fragmentarium/ui/info/Details.test.tsx index 28c1a2f4c..d19301f9f 100644 --- a/src/fragmentarium/ui/info/Details.test.tsx +++ b/src/fragmentarium/ui/info/Details.test.tsx @@ -19,13 +19,18 @@ import { joinFactory } from 'test-support/join-fixtures' import { PartialDate } from 'fragmentarium/domain/archaeology' import { Periods } from 'common/period' import FragmentService from 'fragmentarium/application/FragmentService' +import DossiersService from 'dossiers/application/DossiersService' jest.mock('fragmentarium/application/FragmentService') const MockFragmentService = FragmentService as jest.Mock< jest.Mocked > +const MockDossiersService = DossiersService as jest.Mock< + jest.Mocked +> const fragmentService = new MockFragmentService() +const dossiersService = new MockDossiersService() const updateGenres = jest.fn() const updateScript = jest.fn() @@ -44,6 +49,7 @@ async function renderDetails() { updateDate={updateDate} updateDatesInText={updateDatesInText} fragmentService={fragmentService} + dossiersService={dossiersService} /> ) diff --git a/src/fragmentarium/ui/info/Details.tsx b/src/fragmentarium/ui/info/Details.tsx index 8997ce8a6..cc823e7b3 100644 --- a/src/fragmentarium/ui/info/Details.tsx +++ b/src/fragmentarium/ui/info/Details.tsx @@ -13,7 +13,8 @@ import Bluebird from 'bluebird' import { MesopotamianDate } from 'chronology/domain/Date' import DatesInTextSelection from 'chronology/ui/DateEditor/DatesInTextSelection' import { DateRange, PartialDate } from 'fragmentarium/domain/archaeology' -import { FragmentDossierRecordsDisplay } from 'dossiers/ui/DossiersDisplay' +import FragmentDossierRecordsDisplay from 'dossiers/ui/DossiersDisplay' +import DossiersService from 'dossiers/application/DossiersService' interface Props { readonly fragment: Fragment @@ -153,6 +154,7 @@ interface DetailsProps { datesInText: readonly MesopotamianDate[] ) => Bluebird readonly fragmentService: FragmentService + readonly dossiersService: DossiersService } function Details({ @@ -162,6 +164,7 @@ function Details({ updateDate, updateDatesInText, fragmentService, + dossiersService, }: DetailsProps): JSX.Element { const findspotString = fragment.archaeology?.findspot?.toString() return ( @@ -196,7 +199,10 @@ function Details({
      2. {`Findspot: ${findspotString || '-'}`}
      3. - +
      4. ) => void } @@ -29,6 +31,7 @@ interface Props { export default function Info({ fragment, fragmentService, + dossiersService, afoRegisterService, onSave, }: Props): JSX.Element { @@ -49,6 +52,7 @@ export default function Info({ updateDate={updateDate} updateDatesInText={updateDatesInText} fragmentService={fragmentService} + dossiersService={dossiersService} />
        diff --git a/src/index.tsx b/src/index.tsx index 8998d7064..18c4f906a 100644 --- a/src/index.tsx +++ b/src/index.tsx @@ -31,6 +31,8 @@ import AfoRegisterService from 'afo-register/application/AfoRegisterService' import './index.sass' import { FindspotService } from 'fragmentarium/application/FindspotService' import { ApiFindspotRepository } from 'fragmentarium/infrastructure/FindspotRepository' +import DossiersService from 'dossiers/application/DossiersService' +import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' if (process.env.REACT_APP_SENTRY_DSN && process.env.NODE_ENV) { SentryErrorReporter.init( @@ -64,6 +66,7 @@ function InjectedApp(): JSX.Element { const bibliographyRepository = new BibliographyRepository(apiClient) const afoRegisterRepository = new AfoRegisterRepository(apiClient) const findspotRepository = new ApiFindspotRepository(apiClient) + const dossiersRepository = new DossiersRepository(apiClient) const bibliographyService = new BibliographyService(bibliographyRepository) const fragmentService = new FragmentService( @@ -87,6 +90,7 @@ function InjectedApp(): JSX.Element { bibliographyService ) const afoRegisterService = new AfoRegisterService(afoRegisterRepository) + const dossiersService = new DossiersService(dossiersRepository) const findspotService = new FindspotService(findspotRepository) return ( ) diff --git a/src/router/fragmentariumRoutes.tsx b/src/router/fragmentariumRoutes.tsx index a5df00dff..0a9aeea14 100644 --- a/src/router/fragmentariumRoutes.tsx +++ b/src/router/fragmentariumRoutes.tsx @@ -22,6 +22,7 @@ import BibliographyService from 'bibliography/application/BibliographyService' import { FindspotService } from 'fragmentarium/application/FindspotService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' import NotFoundPage from 'NotFoundPage' +import DossiersService from 'dossiers/application/DossiersService' function parseStringParam(location: Location, param: string): string | null { const value = parse(location.search)[param] return _.isArray(value) ? value.join('') : value @@ -51,6 +52,7 @@ export default function FragmentariumRoutes({ fragmentService, fragmentSearchService, afoRegisterService, + dossiersService, textService, wordService, findspotService, @@ -65,6 +67,7 @@ export default function FragmentariumRoutes({ wordService: WordService findspotService: FindspotService afoRegisterService: AfoRegisterService + dossiersService: DossiersService signService: SignService bibliographyService: BibliographyService fragmentSlugs?: FragmentSlugs @@ -141,6 +144,7 @@ export default function FragmentariumRoutes({ wordService={wordService} findspotService={findspotService} afoRegisterService={afoRegisterService} + dossiersService={dossiersService} session={session} {...parseFragmentParams(match, location)} /> diff --git a/src/router/router.tsx b/src/router/router.tsx index 871534cf4..f927ce0eb 100644 --- a/src/router/router.tsx +++ b/src/router/router.tsx @@ -32,6 +32,7 @@ import { HelmetProvider } from 'react-helmet-async' import { FindspotService } from 'fragmentarium/application/FindspotService' import Footer from 'Footer' import './router.sass' +import DossiersService from 'dossiers/application/DossiersService' export interface Services { wordService: WordService @@ -43,6 +44,7 @@ export interface Services { markupService: MarkupService cachedMarkupService: CachedMarkupService afoRegisterService: AfoRegisterService + dossiersService: DossiersService findspotService: FindspotService } diff --git a/src/test-support/AppDriver.tsx b/src/test-support/AppDriver.tsx index dc04e688c..ac7b85f94 100644 --- a/src/test-support/AppDriver.tsx +++ b/src/test-support/AppDriver.tsx @@ -35,6 +35,8 @@ 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' +import DossiersService from 'dossiers/application/DossiersService' +import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' export function getServices( api: any = FakeApi @@ -48,6 +50,7 @@ export function getServices( markupService: MarkupService cachedMarkupService: CachedMarkupService afoRegisterService: AfoRegisterService + dossiersService: DossiersService findspotService: FindspotService } { const wordRepository = new WordRepository(api) @@ -73,10 +76,12 @@ export function getServices( ) const signsRepository = new SignRepository(api) const afoRegisterRepository = new AfoRegisterRepository(api) + const dossiersRepository = new DossiersRepository(api) const signService = new SignService(signsRepository) const markupService = new MarkupService(api, bibliographyService) const cachedMarkupService = new CachedMarkupService(api, bibliographyService) const afoRegisterService = new AfoRegisterService(afoRegisterRepository) + const dossiersService = new DossiersService(dossiersRepository) const findspotService = new FindspotService(findspotRepository) return { signService, @@ -88,6 +93,7 @@ export function getServices( markupService, cachedMarkupService, afoRegisterService, + dossiersService, findspotService, } } From 8d5432bdee413390c3534f3a5e14b78f72b21d41 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 31 Dec 2024 14:13:46 +0000 Subject: [PATCH 05/14] Update infrastructure, tests & display --- .../application/DossiersService.test.ts | 8 +++---- src/dossiers/domain/DossierRecord.ts | 6 +++--- .../infrastructure/DossiersRepository.test.ts | 21 +++---------------- .../infrastructure/DossiersRepository.ts | 2 +- src/dossiers/ui/DossierDisplay.test.tsx | 3 ++- src/dossiers/ui/DossiersDisplay.tsx | 20 +++++++++++------- src/fragmentarium/ui/info/Info.tsx | 6 ++++++ 7 files changed, 32 insertions(+), 34 deletions(-) diff --git a/src/dossiers/application/DossiersService.test.ts b/src/dossiers/application/DossiersService.test.ts index 002304fe4..7cbfebb7f 100644 --- a/src/dossiers/application/DossiersService.test.ts +++ b/src/dossiers/application/DossiersService.test.ts @@ -3,9 +3,9 @@ import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' import DossierRecord from 'dossiers/domain/DossierRecord' import DossiersService from 'dossiers/application/DossiersService' import { stringify } from 'query-string' -import { ReferenceType } from 'bibliography/domain/Reference' import { Provenances } from 'corpus/domain/provenance' import { PeriodModifiers, Periods } from 'common/period' +import { referenceFactory } from 'test-support/bibliography-fixtures' jest.mock('dossiers/infrastructure/DossiersRepository') const dossiersRepository = new (DossiersRepository as jest.Mock)() @@ -25,7 +25,7 @@ const resultStub = { periodModifier: PeriodModifiers.None, uncertain: false, }, - references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], + references: [referenceFactory.build()], } const query = { ids: ['test'] } @@ -34,10 +34,10 @@ const entry = new DossierRecord(resultStub) const testData: TestData[] = [ new TestData( 'queryByIds', - [stringify(query)], + [stringify(query, { arrayFormat: 'index' })], dossiersRepository.queryByIds, [entry], - [stringify(query)], + [stringify(query, { arrayFormat: 'index' })], Promise.resolve([entry]) ), ] diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts index 31ba8d2f9..a04eaa071 100644 --- a/src/dossiers/domain/DossierRecord.ts +++ b/src/dossiers/domain/DossierRecord.ts @@ -1,5 +1,5 @@ import { immerable } from 'immer' -import { ReferenceType } from 'bibliography/domain/Reference' +import Reference from 'bibliography/domain/Reference' import { Provenance } from 'corpus/domain/provenance' import { Script } from 'fragmentarium/domain/fragment' @@ -12,7 +12,7 @@ interface DossierRecordData { readonly relatedKings?: number[] readonly provenance?: Provenance readonly script?: Script - readonly references?: ReferenceType[] + readonly references?: Reference[] } export default class DossierRecord { @@ -26,7 +26,7 @@ export default class DossierRecord { readonly relatedKings: number[] readonly provenance?: Provenance readonly script?: Script - readonly references: ReferenceType[] + readonly references: Reference[] constructor({ id, diff --git a/src/dossiers/infrastructure/DossiersRepository.test.ts b/src/dossiers/infrastructure/DossiersRepository.test.ts index a0eb01538..e73f1b0de 100644 --- a/src/dossiers/infrastructure/DossiersRepository.test.ts +++ b/src/dossiers/infrastructure/DossiersRepository.test.ts @@ -1,11 +1,9 @@ -import { testDelegation, TestData } from 'test-support/utils' import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' import DossierRecord from 'dossiers/domain/DossierRecord' -import { stringify } from 'query-string' import ApiClient from 'http/ApiClient' import { PeriodModifiers, Periods } from 'common/period' -import { ReferenceType } from 'bibliography/domain/Reference' import { Provenances } from 'corpus/domain/provenance' +import { referenceFactory } from 'test-support/bibliography-fixtures' jest.mock('http/ApiClient') jest.mock('dossiers/application/DossiersService') @@ -26,25 +24,12 @@ const resultStub = { periodModifier: PeriodModifiers.None, uncertain: false, }, - references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], + references: [referenceFactory.build()], } const query = ['test', 'test2'] const record = new DossierRecord(resultStub) -const testData: TestData[] = [ - new TestData( - 'queryByIds', - [stringify(query)], - apiClient.fetchJson, - [record], - ['/dossiers?ids=0%3Dtest%261%3Dtest2', false], - Promise.resolve([resultStub]) - ), -] - -describe('dossiersService', () => testDelegation(dossiersRepository, testData)) - describe('DossiersRepository - search by ids', () => { it('handles search without errors', () => { apiClient.fetchJson.mockResolvedValueOnce([resultStub]) @@ -54,7 +39,7 @@ describe('DossiersRepository - search by ids', () => { }) expect(apiClient.fetchJson).toHaveBeenCalledWith( - '/dossiers?ids=test&ids=test2', + '/dossiers?ids[]=test&ids[]=test2', false ) }) diff --git a/src/dossiers/infrastructure/DossiersRepository.ts b/src/dossiers/infrastructure/DossiersRepository.ts index 8fa947430..efb1673a7 100644 --- a/src/dossiers/infrastructure/DossiersRepository.ts +++ b/src/dossiers/infrastructure/DossiersRepository.ts @@ -11,7 +11,7 @@ export default class DossiersRepository { } queryByIds(query: string[]): Promise { - const queryString = stringify({ ids: query }, { arrayFormat: 'index' }) + const queryString = stringify({ ids: query }, { arrayFormat: 'bracket' }) return this.apiClient .fetchJson(`/dossiers?${queryString}`, false) .then((result) => result.map((data) => new DossierRecord(data))) diff --git a/src/dossiers/ui/DossierDisplay.test.tsx b/src/dossiers/ui/DossierDisplay.test.tsx index c3e9b1243..80ddf1606 100644 --- a/src/dossiers/ui/DossierDisplay.test.tsx +++ b/src/dossiers/ui/DossierDisplay.test.tsx @@ -11,6 +11,7 @@ import FragmentDossierRecordsDisplay, { import { Provenances } from 'corpus/domain/provenance' import { PeriodModifiers, Periods } from 'common/period' import { fragmentFactory } from 'test-support/fragment-fixtures' +import { referenceFactory } from 'test-support/bibliography-fixtures' jest.mock('common/MarkdownAndHtmlToHtml', () => ({ __esModule: true, @@ -32,7 +33,7 @@ const mockRecord = new DossierRecord({ periodModifier: PeriodModifiers.None, uncertain: false, }, - references: ['EDITION', 'DISCUSSION'], + references: referenceFactory.buildList(3), }) describe('DossierRecordDisplay', () => { diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index 76a9758eb..a8f4a9af7 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -34,13 +34,19 @@ export function DossierRecordsListDisplay({ return <> } return ( -
          - {records.map((record, index) => ( -
        1. - -
        2. - ))} -
        +
        +

        Dossiers

        +
          + {records.map((record, index) => ( +
        1. + +
        2. + ))} +
        +
        ) } diff --git a/src/fragmentarium/ui/info/Info.tsx b/src/fragmentarium/ui/info/Info.tsx index cde8f9a7e..7508ca945 100644 --- a/src/fragmentarium/ui/info/Info.tsx +++ b/src/fragmentarium/ui/info/Info.tsx @@ -54,6 +54,12 @@ export default function Info({ fragmentService={fragmentService} dossiersService={dossiersService} /> + {!_.isEmpty(fragment.colophon) && ( +
        +

        Colophon

        + +
        + )}

        References

        From c144bc57206500c781266e12b0ec3863be736190 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Sun, 5 Jan 2025 22:28:48 +0000 Subject: [PATCH 06/14] Update dossier display --- src/dossiers/domain/DossierRecord.test.ts | 1 + src/dossiers/domain/DossierRecord.ts | 64 ++++++++++++++++++----- src/dossiers/ui/DossiersDisplay.tsx | 36 +++++++++++-- 3 files changed, 84 insertions(+), 17 deletions(-) diff --git a/src/dossiers/domain/DossierRecord.test.ts b/src/dossiers/domain/DossierRecord.test.ts index 7ac5438da..95ee8d810 100644 --- a/src/dossiers/domain/DossierRecord.test.ts +++ b/src/dossiers/domain/DossierRecord.test.ts @@ -17,6 +17,7 @@ describe('DossierRecord', () => { periodModifier: PeriodModifiers.None, uncertain: false, }, + // ToDo: Change `ReferenceType` to `Reference` references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], } diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts index a04eaa071..f02606c01 100644 --- a/src/dossiers/domain/DossierRecord.ts +++ b/src/dossiers/domain/DossierRecord.ts @@ -1,18 +1,23 @@ import { immerable } from 'immer' -import Reference from 'bibliography/domain/Reference' import { Provenance } from 'corpus/domain/provenance' -import { Script } from 'fragmentarium/domain/fragment' +import { Script, ScriptDto } from 'fragmentarium/domain/fragment' +import Citation from 'bibliography/domain/Citation' +import Reference from 'bibliography/domain/Reference' +import { ReferenceDto } from 'bibliography/domain/referenceDto' +import createReference from 'bibliography/application/createReference' +import { PeriodModifiers, Periods } from 'common/period' +import { createScript } from 'fragmentarium/infrastructure/FragmentRepository' -interface DossierRecordData { - readonly id: string +interface DossierRecordDto { + readonly _id: string readonly description?: string readonly isApproximateDate?: boolean readonly yearRangeFrom?: number readonly yearRangeTo?: number readonly relatedKings?: number[] readonly provenance?: Provenance - readonly script?: Script - readonly references?: Reference[] + readonly script?: ScriptDto + readonly references?: ReferenceDto[] } export default class DossierRecord { @@ -29,7 +34,7 @@ export default class DossierRecord { readonly references: Reference[] constructor({ - id, + _id, description, isApproximateDate = false, yearRangeFrom, @@ -38,23 +43,56 @@ export default class DossierRecord { provenance, script, references = [], - }: DossierRecordData) { - this.id = id + }: DossierRecordDto) { + this.id = _id this.description = description this.isApproximateDate = isApproximateDate this.yearRangeFrom = yearRangeFrom this.yearRangeTo = yearRangeTo this.relatedKings = relatedKings this.provenance = provenance - this.script = script - this.references = references + this.script = script && createScript(script) + this.references = references.map((referenceDto) => + createReference(referenceDto) + ) } toMarkdownString(): string { - return `${this.YearsToMarkdownString()}` + const parts = [ + `**Name**: ${this.id}`, + this.description ? `\n\n **Description**: ${this.description}` : null, + `\n\n **Date**: ${this.yearsToMarkdownString()}`, + this.relatedKings.length > 0 + ? `\n\n **Related Kings**: ${this.relatedKings.join(', ')}` + : null, + this.provenance ? `\n\n **Provenance**: ${this.provenance}` : null, + this.script ? `\n\n **Script**: ${this.scriptToMarkdownString()}` : null, + this.references.length > 0 + ? `\n\n **References**: ${this.references + .map((reference) => Citation.for(reference).getMarkdown()) + .join('\n')}` + : null, + ] + return parts.filter((part) => part !== null).join('') + } + + private scriptToMarkdownString(): string { + const script = this.script + if (!script) { + return '' + } + const periodModifier = + script.periodModifier !== PeriodModifiers.None + ? script.periodModifier.name + : null + const period = script.period !== Periods.None ? script.period.name : null + const uncertain = script.uncertain ? '(?)' : null + return [periodModifier, period, uncertain] + .filter((part) => part !== null) + .join(' ') } - private YearsToMarkdownString(): string { + private yearsToMarkdownString(): string { const yearRangeFrom = this.formatYear(this.yearRangeFrom) const yearRangeTo = this.formatYear(this.yearRangeTo) diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index a8f4a9af7..ec1939658 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -6,6 +6,7 @@ import withData from 'http/withData' import DossiersService from 'dossiers/application/DossiersService' import _ from 'lodash' import Bluebird from 'bluebird' +import { OverlayTrigger, Popover } from 'react-bootstrap' export function DossierRecordDisplay({ record, @@ -33,20 +34,47 @@ export function DossierRecordsListDisplay({ if (records.length < 1) { return <> } + + function getDossierPopover({ + record, + index, + }: { + record: DossierRecord + index: number + }): JSX.Element { + return ( + + + + + + ) + } + return ( -
        -

        Dossiers

        +
        + Dossiers:
          {records.map((record, index) => (
        1. - + + + {record.id} + +
        2. ))}
        -
        +
        ) } From f973c15b9bca6438417b6485dfde865f964db6be Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 7 Jan 2025 18:51:42 +0000 Subject: [PATCH 07/14] Update display & tests --- .../ManuscriptPopover.test.tsx.snap | 2 +- src/dossiers/domain/DossierRecord.test.ts | 29 +++-- src/dossiers/domain/DossierRecord.ts | 40 ++++--- src/dossiers/ui/DossiersDisplay.sass | 18 +++ ...play.test.tsx => DossiersDisplay.test.tsx} | 53 +++++---- src/dossiers/ui/DossiersDisplay.tsx | 110 +++++++++++------- .../LatestTransliterations.test.tsx.snap | 16 +-- .../FragmentariumSearch.test.tsx.snap | 8 +- .../ui/__snapshots__/markup.test.tsx.snap | 2 +- 9 files changed, 173 insertions(+), 105 deletions(-) create mode 100644 src/dossiers/ui/DossiersDisplay.sass rename src/dossiers/ui/{DossierDisplay.test.tsx => DossiersDisplay.test.tsx} (64%) diff --git a/src/corpus/ui/__snapshots__/ManuscriptPopover.test.tsx.snap b/src/corpus/ui/__snapshots__/ManuscriptPopover.test.tsx.snap index 417e322d3..6a122325b 100644 --- a/src/corpus/ui/__snapshots__/ManuscriptPopover.test.tsx.snap +++ b/src/corpus/ui/__snapshots__/ManuscriptPopover.test.tsx.snap @@ -19,7 +19,7 @@ exports[`Show manuscript line details 1`] = ` - Estrada & Wolfe, 2069: 7477711662481408-4847418310918144 + Estrada & Wolfe, 2070: 7477711662481408-4847418310918144 [ l. 4'.2., 3'. ] diff --git a/src/dossiers/domain/DossierRecord.test.ts b/src/dossiers/domain/DossierRecord.test.ts index 95ee8d810..49d0e6400 100644 --- a/src/dossiers/domain/DossierRecord.test.ts +++ b/src/dossiers/domain/DossierRecord.test.ts @@ -1,11 +1,13 @@ import DossierRecord from 'dossiers/domain/DossierRecord' -import { ReferenceType } from 'bibliography/domain/Reference' import { PeriodModifiers, Periods } from 'common/period' import { Provenances } from 'corpus/domain/provenance' +import { referenceDtoFactory } from 'test-support/bibliography-fixtures' +import Reference from 'bibliography/domain/Reference' describe('DossierRecord', () => { - const mockRecord = { - id: 'test', + const references = [referenceDtoFactory.build(), referenceDtoFactory.build()] + const mockRecordDto = { + _id: 'test', description: 'some desciption', isApproximateDate: true, yearRangeFrom: -500, @@ -13,17 +15,16 @@ describe('DossierRecord', () => { relatedKings: [10.2, 11], provenance: Provenances.Assyria, script: { - period: Periods['Neo-Assyrian'], - periodModifier: PeriodModifiers.None, + period: 'Neo-Assyrian', + periodModifier: 'None', uncertain: false, }, - // ToDo: Change `ReferenceType` to `Reference` - references: ['EDITION' as ReferenceType, 'DISCUSSION' as ReferenceType], + references, } describe('constructor', () => { it('should initialize properties correctly', () => { - const record = new DossierRecord(mockRecord) + const record = new DossierRecord(mockRecordDto) expect(record.id).toEqual('test') expect(record.description).toEqual('some desciption') expect(record.isApproximateDate).toEqual(true) @@ -36,7 +37,17 @@ describe('DossierRecord', () => { periodModifier: PeriodModifiers.None, uncertain: false, }) - expect(record.references).toEqual(['EDITION', 'DISCUSSION']) + expect(record.references).toEqual( + references.map( + (reference) => + new Reference( + reference.type, + reference.pages, + reference.notes, + reference.linesCited + ) + ) + ) }) }) }) diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts index f02606c01..a60e40bb5 100644 --- a/src/dossiers/domain/DossierRecord.ts +++ b/src/dossiers/domain/DossierRecord.ts @@ -59,21 +59,27 @@ export default class DossierRecord { toMarkdownString(): string { const parts = [ - `**Name**: ${this.id}`, - this.description ? `\n\n **Description**: ${this.description}` : null, - `\n\n **Date**: ${this.yearsToMarkdownString()}`, - this.relatedKings.length > 0 - ? `\n\n **Related Kings**: ${this.relatedKings.join(', ')}` - : null, - this.provenance ? `\n\n **Provenance**: ${this.provenance}` : null, - this.script ? `\n\n **Script**: ${this.scriptToMarkdownString()}` : null, - this.references.length > 0 - ? `\n\n **References**: ${this.references - .map((reference) => Citation.for(reference).getMarkdown()) - .join('\n')}` - : null, + { name: 'Description', value: this.description }, + { name: 'Period', value: this.yearsToMarkdownString() }, + { + name: 'Related Kings', + value: + this.relatedKings.length > 0 ? this.relatedKings.join(', ') : null, + }, + { name: 'Provenance', value: this.provenance }, + { name: 'Script', value: this.scriptToMarkdownString() }, + { + name: 'Bibliography', + value: this.references + .map((reference) => Citation.for(reference).getMarkdown()) + .join('; '), + }, ] - return parts.filter((part) => part !== null).join('') + return parts + .filter((part) => !!part.value) + .map((part) => `**${part.name}**: ${part.value}`) + .join('. ') + .replaceAll('..', '.') } private scriptToMarkdownString(): string { @@ -92,12 +98,12 @@ export default class DossierRecord { .join(' ') } - private yearsToMarkdownString(): string { + private yearsToMarkdownString(): string | null { const yearRangeFrom = this.formatYear(this.yearRangeFrom) const yearRangeTo = this.formatYear(this.yearRangeTo) if (!yearRangeFrom) { - return '' + return null } if (this.isApproximateDate) { @@ -127,6 +133,6 @@ export default class DossierRecord { if (yearRangeFrom === yearRangeTo || !yearRangeTo) { return yearRangeFrom } - return `${yearRangeFrom} - ${yearRangeTo}` + return `${yearRangeFrom} – ${yearRangeTo}` } } diff --git a/src/dossiers/ui/DossiersDisplay.sass b/src/dossiers/ui/DossiersDisplay.sass new file mode 100644 index 000000000..5f320cef2 --- /dev/null +++ b/src/dossiers/ui/DossiersDisplay.sass @@ -0,0 +1,18 @@ +.dossier-records__item + cursor: pointer + margin-right: 8px + text-decoration: underline + text-decoration-style: dotted + + &__active + text-decoration: underline + text-decoration-style: dotted + font-weight: 600 + +.reference-popover__popover + opacity: 0 + transition: opacity 0.4s linear + + &.show + transition: opacity 0.4s linear + opacity: 1 diff --git a/src/dossiers/ui/DossierDisplay.test.tsx b/src/dossiers/ui/DossiersDisplay.test.tsx similarity index 64% rename from src/dossiers/ui/DossierDisplay.test.tsx rename to src/dossiers/ui/DossiersDisplay.test.tsx index 80ddf1606..11dedcf2c 100644 --- a/src/dossiers/ui/DossierDisplay.test.tsx +++ b/src/dossiers/ui/DossiersDisplay.test.tsx @@ -9,9 +9,10 @@ import FragmentDossierRecordsDisplay, { DossierRecordsListDisplay, } from './DossiersDisplay' import { Provenances } from 'corpus/domain/provenance' -import { PeriodModifiers, Periods } from 'common/period' import { fragmentFactory } from 'test-support/fragment-fixtures' -import { referenceFactory } from 'test-support/bibliography-fixtures' +import { referenceDtoFactory } from 'test-support/bibliography-fixtures' +import { act } from 'react-dom/test-utils' +import userEvent from '@testing-library/user-event' jest.mock('common/MarkdownAndHtmlToHtml', () => ({ __esModule: true, @@ -20,8 +21,8 @@ jest.mock('common/MarkdownAndHtmlToHtml', () => ({ ), })) -const mockRecord = new DossierRecord({ - id: 'test', +const mockRecordDto = { + _id: 'test', description: 'Test description', isApproximateDate: true, yearRangeFrom: -500, @@ -29,12 +30,14 @@ const mockRecord = new DossierRecord({ relatedKings: [10.2, 11], provenance: Provenances['Assyria'], script: { - period: Periods['Neo-Assyrian'], - periodModifier: PeriodModifiers.None, + period: 'Neo-Assyrian', + periodModifier: 'None', uncertain: false, }, - references: referenceFactory.buildList(3), -}) + references: referenceDtoFactory.buildList(3), +} + +const mockRecord = new DossierRecord(mockRecordDto) describe('DossierRecordDisplay', () => { it('renders correctly with a record', () => { @@ -49,17 +52,20 @@ describe('DossierRecordsListDisplay', () => { expect(screen.queryByRole('list')).not.toBeInTheDocument() }) - it('renders a list of records', () => { + it('renders a list of records', async () => { const records = [ mockRecord, - new DossierRecord({ ...mockRecord, id: 'test2' }), + new DossierRecord({ ...mockRecordDto, _id: 'test2' }), ] render() - expect(screen.getAllByRole('listitem')).toHaveLength(records.length) - expect(screen.getAllByText('ca. 500 BCE - 470 BCE')).toHaveLength( - records.length - ) + await act(async () => { + const dossierSpan = screen.getByText('Dossier: test') + userEvent.click(dossierSpan) + }) + + expect(screen.getAllByText(/Dossier: test/)).toHaveLength(records.length) + expect(screen.getByText(/ca. 500 BCE - 470 BCE/)).toBeInTheDocument() }) }) @@ -72,13 +78,18 @@ describe('withData HOC integration', () => { dossiers: [{ dossierId: 'test', isUncertain: true }], }) - render( - - ) - + await act(async () => { + render( + + ) + }) + await act(async () => { + const dossierSpan = screen.getByText('Dossier: test') + userEvent.click(dossierSpan) + }) await screen.findByText(mockRecord.toMarkdownString()) expect(mockDossiersService.queryByIds).toHaveBeenCalledWith(['test']) }) diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index ec1939658..c50d84bdf 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -1,19 +1,20 @@ -import React from 'react' +import React, { useRef, useState } from 'react' import DossierRecord from 'dossiers/domain/DossierRecord' import MarkdownAndHtmlToHtml from 'common/MarkdownAndHtmlToHtml' -import { Fragment } from 'fragmentarium/domain/fragment' import withData from 'http/withData' import DossiersService from 'dossiers/application/DossiersService' import _ from 'lodash' import Bluebird from 'bluebird' -import { OverlayTrigger, Popover } from 'react-bootstrap' +import { Popover, Overlay } from 'react-bootstrap' +import { Fragment } from 'fragmentarium/domain/fragment' +import './DossiersDisplay.sass' export function DossierRecordDisplay({ record, index, }: { record: DossierRecord - index: string | number + index: number }): JSX.Element { return ( > +}): JSX.Element { + const { record, index, activeDossier, setActiveDossier } = props + const target = useRef(null) + const isActive = activeDossier === index + + const infoSpan = ( + setActiveDossier(isActive ? null : index)} + ref={target} + > + {`Dossier: ${record.id}`} + + ) + + const popover = ( + + {record.id} + + + + + ) + + return ( + <> + {infoSpan} + setActiveDossier(index)} + onHide={() => setActiveDossier(null)} + rootClose={true} + rootCloseEvent={'click'} + > + {popover} + + + ) +} + export function DossierRecordsListDisplay({ data, }: { data: { records: readonly DossierRecord[] } } & React.OlHTMLAttributes): JSX.Element { const { records } = data + const [activeDossier, setActiveDossier] = useState(null) if (records.length < 1) { return <> } - - function getDossierPopover({ - record, - index, - }: { - record: DossierRecord - index: number - }): JSX.Element { - return ( - - - - - - ) - } - return ( -
        - Dossiers: -
          - {records.map((record, index) => ( -
        1. - - - {record.id} - - -
        2. - ))} -
        -
        + <> + {records.map((record, index) => ( + + ))} + ) } diff --git a/src/fragmentarium/ui/front-page/__snapshots__/LatestTransliterations.test.tsx.snap b/src/fragmentarium/ui/front-page/__snapshots__/LatestTransliterations.test.tsx.snap index 5abcb6fb5..a0944b46b 100644 --- a/src/fragmentarium/ui/front-page/__snapshots__/LatestTransliterations.test.tsx.snap +++ b/src/fragmentarium/ui/front-page/__snapshots__/LatestTransliterations.test.tsx.snap @@ -80,9 +80,9 @@ exports[`Snapshot 1`] = ` , )
      5. @@ -129,7 +129,7 @@ exports[`Snapshot 1`] = ` - Fanti & Carr, 2037: 8970824935538688-3796097900216320 + Fanti & Carr, 2038: 8970824935538688-3796097900216320 [ l. 4'.2., 2. ] @@ -144,7 +144,7 @@ exports[`Snapshot 1`] = ` - Hall & Reid, 2090: 7020923936833536-4895425479835648 + Hall & Reid, 2091: 7020923936833536-4895425479835648 [ l. 4'.2., 3'. ] @@ -5905,9 +5905,9 @@ exports[`Snapshot 1`] = ` , ) @@ -5954,7 +5954,7 @@ exports[`Snapshot 1`] = ` - Checcucci & Tomlinson, 2026: 4727451873705984-8024100712742912 + Checcucci & Tomlinson, 2027: 4727451873705984-8024100712742912 [ l. 3'., 1. ] @@ -5973,7 +5973,7 @@ exports[`Snapshot 1`] = ` - Chirici & Borchi, 2055: 851575803215872-8906036102561792 + Chirici & Borchi, 2056: 851575803215872-8906036102561792 [ l. 1., 2. ] diff --git a/src/fragmentarium/ui/search/__snapshots__/FragmentariumSearch.test.tsx.snap b/src/fragmentarium/ui/search/__snapshots__/FragmentariumSearch.test.tsx.snap index 3ed458fcd..24c5caf60 100644 --- a/src/fragmentarium/ui/search/__snapshots__/FragmentariumSearch.test.tsx.snap +++ b/src/fragmentarium/ui/search/__snapshots__/FragmentariumSearch.test.tsx.snap @@ -871,7 +871,7 @@ exports[`Searching fragments by transliteration Displays corpus results when cli - Duran & Boon, 2075: 1233275364311040-5892501091123200 + Duran & Boon, 2076: 1233275364311040-5892501091123200 [ l. 4'.2., 1. ] @@ -890,7 +890,7 @@ exports[`Searching fragments by transliteration Displays corpus results when cli - Hansen & van Ommen, 2050: 522493473325056-5682457787498496 + Hansen & van Ommen, 2051: 522493473325056-5682457787498496 [ l. 4'.2., 1. ] @@ -6691,7 +6691,7 @@ exports[`Searching fragments by transliteration Displays corpus results when cli - Cardini & Verheul, 2114: 1306047899762688-2977660099624960 + Cardini & Verheul, 2115: 1306047899762688-2977660099624960 [ l. 3'., 4'.2. ] @@ -6706,7 +6706,7 @@ exports[`Searching fragments by transliteration Displays corpus results when cli - Pratesi & Dupont, 2028: 3232151373873152-6290998736977920 + Pratesi & Dupont, 2029: 3232151373873152-6290998736977920 [ l. 3'., 1. ] diff --git a/src/transliteration/ui/__snapshots__/markup.test.tsx.snap b/src/transliteration/ui/__snapshots__/markup.test.tsx.snap index df48a0dc2..78a4b53df 100644 --- a/src/transliteration/ui/__snapshots__/markup.test.tsx.snap +++ b/src/transliteration/ui/__snapshots__/markup.test.tsx.snap @@ -40,7 +40,7 @@ exports[`Markup 1`] = ` - Dickinson & Falciani, 2063: 6831522153758720-8434983175716864 + Dickinson & Falciani, 2064: 6831522153758720-8434983175716864 [ l. 4'.2., 1. ] From 6e680bc1a37f078adf2cc665dd621abfaa3c7929 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Wed, 8 Jan 2025 20:17:59 +0000 Subject: [PATCH 08/14] Update display & add tests, refactor --- src/dossiers/domain/DossierRecord.test.ts | 119 ++++++++++++++++++- src/dossiers/domain/DossierRecord.ts | 44 +++---- src/dossiers/domain/DossierReference.test.ts | 20 ++++ src/dossiers/ui/DossiersDisplay.sass | 4 +- src/dossiers/ui/DossiersDisplay.test.tsx | 6 +- src/dossiers/ui/DossiersDisplay.tsx | 106 ++++++++++++----- 6 files changed, 230 insertions(+), 69 deletions(-) create mode 100644 src/dossiers/domain/DossierReference.test.ts diff --git a/src/dossiers/domain/DossierRecord.test.ts b/src/dossiers/domain/DossierRecord.test.ts index 49d0e6400..1ff6ee862 100644 --- a/src/dossiers/domain/DossierRecord.test.ts +++ b/src/dossiers/domain/DossierRecord.test.ts @@ -8,7 +8,7 @@ describe('DossierRecord', () => { const references = [referenceDtoFactory.build(), referenceDtoFactory.build()] const mockRecordDto = { _id: 'test', - description: 'some desciption', + description: 'some description', isApproximateDate: true, yearRangeFrom: -500, yearRangeTo: -470, @@ -22,11 +22,11 @@ describe('DossierRecord', () => { references, } - describe('constructor', () => { - it('should initialize properties correctly', () => { + describe('Constructor', () => { + it('initialize properties correctly', () => { const record = new DossierRecord(mockRecordDto) expect(record.id).toEqual('test') - expect(record.description).toEqual('some desciption') + expect(record.description).toEqual('some description') expect(record.isApproximateDate).toEqual(true) expect(record.yearRangeFrom).toEqual(-500) expect(record.yearRangeTo).toEqual(-470) @@ -50,4 +50,115 @@ describe('DossierRecord', () => { ) }) }) + + describe('toMarkdownString', () => { + it('generate correct Markdown string', () => { + const record = new DossierRecord(mockRecordDto) + const markdown = record.toMarkdownString() + + expect(markdown).toContain('**Description**: some description') + expect(markdown).toContain('**Period**: ca. 500 BCE - 470 BCE') + expect(markdown).toContain('**Related Kings**: 10.2, 11') + expect(markdown).toContain('**Provenance**: Assyria') + expect(markdown).toContain('**Script**: Neo-Assyrian') + expect(markdown).toContain('**Bibliography**:') + }) + + it('handle missing optional fields gracefully', () => { + const record = new DossierRecord({ + ...mockRecordDto, + description: undefined, + yearRangeFrom: undefined, + relatedKings: [], + }) + const markdown = record.toMarkdownString() + + expect(markdown).not.toContain('**Description**') + expect(markdown).not.toContain('**Period**') + expect(markdown).not.toContain('**Related Kings**') + expect(markdown).toContain('**Provenance**: Assyria') + }) + }) + + describe('Year and script formatting via toMarkdownString', () => { + it('should format a single BCE year correctly', () => { + const record = new DossierRecord({ + ...mockRecordDto, + yearRangeFrom: -500, + yearRangeTo: undefined, + }) + const markdown = record.toMarkdownString() + expect(markdown).toContain('**Period**: ca. 500 BCE\n') + }) + + it('format a single CE year correctly', () => { + const record = new DossierRecord({ + ...mockRecordDto, + yearRangeFrom: 500, + yearRangeTo: undefined, + }) + const markdown = record.toMarkdownString() + expect(markdown).toContain('**Period**: ca. 500 CE\n') + }) + + it('format a BCE to CE year range correctly', () => { + const record = new DossierRecord({ + ...mockRecordDto, + yearRangeFrom: -500, + yearRangeTo: 500, + }) + const markdown = record.toMarkdownString() + expect(markdown).toContain('**Period**: ca. 500 BCE - 500 CE') + }) + + it('format an exact date range without approximation', () => { + const record = new DossierRecord({ + ...mockRecordDto, + isApproximateDate: false, + yearRangeFrom: 100, + yearRangeTo: 200, + }) + const markdown = record.toMarkdownString() + expect(markdown).toContain('**Period**: 100 CE - 200 CE') + }) + + it('handle script formatting with period only', () => { + const record = new DossierRecord({ + ...mockRecordDto, + script: { + period: 'Neo-Assyrian', + periodModifier: 'None', + uncertain: false, + }, + }) + const markdown = record.toMarkdownString() + expect(markdown).toContain('**Script**: Neo-Assyrian') + }) + + it('handle script formatting with period and modifier', () => { + const record = new DossierRecord({ + ...mockRecordDto, + script: { + period: 'Neo-Assyrian', + periodModifier: 'Late', + uncertain: false, + }, + }) + const markdown = record.toMarkdownString() + expect(markdown).toContain('**Script**: Late Neo-Assyrian') + }) + + it('handle script formatting with uncertainty', () => { + const record = new DossierRecord({ + ...mockRecordDto, + script: { + period: 'Neo-Assyrian', + periodModifier: 'None', + uncertain: true, + }, + }) + const markdown = record.toMarkdownString() + expect(markdown).toContain('**Script**: Neo-Assyrian (?)') + }) + }) }) diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts index a60e40bb5..7c907fe69 100644 --- a/src/dossiers/domain/DossierRecord.ts +++ b/src/dossiers/domain/DossierRecord.ts @@ -66,7 +66,7 @@ export default class DossierRecord { value: this.relatedKings.length > 0 ? this.relatedKings.join(', ') : null, }, - { name: 'Provenance', value: this.provenance }, + { name: 'Provenance', value: this.provenance?.name }, { name: 'Script', value: this.scriptToMarkdownString() }, { name: 'Bibliography', @@ -75,11 +75,14 @@ export default class DossierRecord { .join('; '), }, ] + console.log(this.references) + console.log( + this.references.map((reference) => Citation.for(reference).getMarkdown()) + ) return parts .filter((part) => !!part.value) .map((part) => `**${part.name}**: ${part.value}`) - .join('. ') - .replaceAll('..', '.') + .join('\n\n') } private scriptToMarkdownString(): string { @@ -99,18 +102,20 @@ export default class DossierRecord { } private yearsToMarkdownString(): string | null { - const yearRangeFrom = this.formatYear(this.yearRangeFrom) - const yearRangeTo = this.formatYear(this.yearRangeTo) - - if (!yearRangeFrom) { + if (!this.yearRangeFrom) { return null } + const years = this.formatYears() + return `${this.isApproximateDate ? 'ca. ' : ''}${years}` + } - if (this.isApproximateDate) { - return this.formatApproximateRange(yearRangeFrom, yearRangeTo) + private formatYears(): string { + const yearFrom = this.formatYear(this.yearRangeFrom) + if (this.yearRangeFrom === this.yearRangeTo || !this.yearRangeTo) { + return yearFrom } - - return this.formatExactRange(yearRangeFrom, yearRangeTo) + const yearTo = this.formatYear(this.yearRangeTo) + return `${yearFrom} - ${yearTo}` } private formatYear(year: number | undefined): string { @@ -118,21 +123,4 @@ export default class DossierRecord { const prefix = year < 0 ? 'BCE' : 'CE' return `${Math.abs(year)} ${prefix}` } - - private formatApproximateRange( - yearRangeFrom: string, - yearRangeTo: string - ): string { - if (yearRangeFrom === yearRangeTo || !yearRangeTo) { - return `ca. ${yearRangeFrom}` - } - return `ca. ${yearRangeFrom} - ${yearRangeTo}` - } - - private formatExactRange(yearRangeFrom: string, yearRangeTo: string): string { - if (yearRangeFrom === yearRangeTo || !yearRangeTo) { - return yearRangeFrom - } - return `${yearRangeFrom} – ${yearRangeTo}` - } } diff --git a/src/dossiers/domain/DossierReference.test.ts b/src/dossiers/domain/DossierReference.test.ts new file mode 100644 index 000000000..7be7a1bf8 --- /dev/null +++ b/src/dossiers/domain/DossierReference.test.ts @@ -0,0 +1,20 @@ +import { DossierReference } from './DossierReference' + +describe('DossierReference', () => { + it('should allow creation of a valid DossierReference object', () => { + const validDossier: DossierReference = { + dossierId: '12345', + isUncertain: true, + } + expect(validDossier.dossierId).toBe('12345') + expect(validDossier.isUncertain).toBe(true) + }) + + it('should allow omission of the optional isUncertain property', () => { + const validDossier: DossierReference = { + dossierId: '12345', + } + expect(validDossier.dossierId).toBe('12345') + expect(validDossier.isUncertain).toBeUndefined() + }) +}) diff --git a/src/dossiers/ui/DossiersDisplay.sass b/src/dossiers/ui/DossiersDisplay.sass index 5f320cef2..67b8d3311 100644 --- a/src/dossiers/ui/DossiersDisplay.sass +++ b/src/dossiers/ui/DossiersDisplay.sass @@ -7,7 +7,6 @@ &__active text-decoration: underline text-decoration-style: dotted - font-weight: 600 .reference-popover__popover opacity: 0 @@ -16,3 +15,6 @@ &.show transition: opacity 0.4s linear opacity: 1 + +.dossier-record p + margin-block-end: 0 \ No newline at end of file diff --git a/src/dossiers/ui/DossiersDisplay.test.tsx b/src/dossiers/ui/DossiersDisplay.test.tsx index 11dedcf2c..4dd2c6b8f 100644 --- a/src/dossiers/ui/DossiersDisplay.test.tsx +++ b/src/dossiers/ui/DossiersDisplay.test.tsx @@ -42,7 +42,7 @@ const mockRecord = new DossierRecord(mockRecordDto) describe('DossierRecordDisplay', () => { it('renders correctly with a record', () => { render() - expect(screen.getByText(mockRecord.toMarkdownString())).toBeInTheDocument() + expect(screen.getByText(/Test description/)).toBeInTheDocument() }) }) @@ -87,10 +87,10 @@ describe('withData HOC integration', () => { ) }) await act(async () => { - const dossierSpan = screen.getByText('Dossier: test') + const dossierSpan = screen.getByText(/Dossier: test/) userEvent.click(dossierSpan) }) - await screen.findByText(mockRecord.toMarkdownString()) + await screen.findByText(/Test description/) expect(mockDossiersService.queryByIds).toHaveBeenCalledWith(['test']) }) }) diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index c50d84bdf..63d2a0ffd 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -25,54 +25,94 @@ export function DossierRecordDisplay({ ) } -function DossierRecordOverlay(props: { - record: DossierRecord +function DossierInfoSpan({ + index, + isActive, + setActiveDossier, + targetRef, + recordId, +}: { index: number - activeDossier: number | null + isActive: boolean setActiveDossier: React.Dispatch> + targetRef: React.RefObject + recordId: string }): JSX.Element { - const { record, index, activeDossier, setActiveDossier } = props - const target = useRef(null) - const isActive = activeDossier === index - - const infoSpan = ( + return ( setActiveDossier(isActive ? null : index)} - ref={target} + ref={targetRef} > - {`Dossier: ${record.id}`} + {`Dossier: ${recordId}`} ) +} - const popover = ( - + setActiveDossier: React.Dispatch> + record: DossierRecord +}): JSX.Element { + return ( + setActiveDossier(index)} + onHide={() => setActiveDossier(null)} + rootClose={true} + rootCloseEvent={'click'} > - {record.id} - - - - + + {record.id} + + + + + ) +} + +function DossierRecordItem(props: { + record: DossierRecord + index: number + activeDossier: number | null + setActiveDossier: React.Dispatch> +}): JSX.Element { + const { record, index, activeDossier, setActiveDossier } = props + const target = useRef(null) + const isActive = activeDossier === index return ( <> - {infoSpan} - setActiveDossier(index)} - onHide={() => setActiveDossier(null)} - rootClose={true} - rootCloseEvent={'click'} - > - {popover} - + + ) } @@ -91,7 +131,7 @@ export function DossierRecordsListDisplay({ return ( <> {records.map((record, index) => ( - From 15310746d506477bba06493c3f84f1bddd595fb0 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 14 Jan 2025 01:20:56 +0000 Subject: [PATCH 09/14] Update display & tests --- src/dossiers/domain/DossierRecord.test.ts | 22 +++++++++---------- src/dossiers/domain/DossierRecord.ts | 16 +++++--------- .../infrastructure/DossiersRepository.test.ts | 14 +++++------- src/dossiers/ui/DossiersDisplay.test.tsx | 3 +-- 4 files changed, 24 insertions(+), 31 deletions(-) diff --git a/src/dossiers/domain/DossierRecord.test.ts b/src/dossiers/domain/DossierRecord.test.ts index 1ff6ee862..46a33aad1 100644 --- a/src/dossiers/domain/DossierRecord.test.ts +++ b/src/dossiers/domain/DossierRecord.test.ts @@ -13,7 +13,7 @@ describe('DossierRecord', () => { yearRangeFrom: -500, yearRangeTo: -470, relatedKings: [10.2, 11], - provenance: Provenances.Assyria, + provenance: 'Assyria', script: { period: 'Neo-Assyrian', periodModifier: 'None', @@ -57,10 +57,10 @@ describe('DossierRecord', () => { const markdown = record.toMarkdownString() expect(markdown).toContain('**Description**: some description') - expect(markdown).toContain('**Period**: ca. 500 BCE - 470 BCE') + expect(markdown).toContain('**Date**: ca. 500 BCE - 470 BCE') expect(markdown).toContain('**Related Kings**: 10.2, 11') expect(markdown).toContain('**Provenance**: Assyria') - expect(markdown).toContain('**Script**: Neo-Assyrian') + expect(markdown).toContain('**Period**: Neo-Assyrian') expect(markdown).toContain('**Bibliography**:') }) @@ -74,7 +74,7 @@ describe('DossierRecord', () => { const markdown = record.toMarkdownString() expect(markdown).not.toContain('**Description**') - expect(markdown).not.toContain('**Period**') + expect(markdown).not.toContain('**Date**') expect(markdown).not.toContain('**Related Kings**') expect(markdown).toContain('**Provenance**: Assyria') }) @@ -88,7 +88,7 @@ describe('DossierRecord', () => { yearRangeTo: undefined, }) const markdown = record.toMarkdownString() - expect(markdown).toContain('**Period**: ca. 500 BCE\n') + expect(markdown).toContain('**Date**: ca. 500 BCE\n') }) it('format a single CE year correctly', () => { @@ -98,7 +98,7 @@ describe('DossierRecord', () => { yearRangeTo: undefined, }) const markdown = record.toMarkdownString() - expect(markdown).toContain('**Period**: ca. 500 CE\n') + expect(markdown).toContain('**Date**: ca. 500 CE\n') }) it('format a BCE to CE year range correctly', () => { @@ -108,7 +108,7 @@ describe('DossierRecord', () => { yearRangeTo: 500, }) const markdown = record.toMarkdownString() - expect(markdown).toContain('**Period**: ca. 500 BCE - 500 CE') + expect(markdown).toContain('**Date**: ca. 500 BCE - 500 CE') }) it('format an exact date range without approximation', () => { @@ -119,7 +119,7 @@ describe('DossierRecord', () => { yearRangeTo: 200, }) const markdown = record.toMarkdownString() - expect(markdown).toContain('**Period**: 100 CE - 200 CE') + expect(markdown).toContain('**Date**: 100 CE - 200 CE') }) it('handle script formatting with period only', () => { @@ -132,7 +132,7 @@ describe('DossierRecord', () => { }, }) const markdown = record.toMarkdownString() - expect(markdown).toContain('**Script**: Neo-Assyrian') + expect(markdown).toContain('**Period**: Neo-Assyrian') }) it('handle script formatting with period and modifier', () => { @@ -145,7 +145,7 @@ describe('DossierRecord', () => { }, }) const markdown = record.toMarkdownString() - expect(markdown).toContain('**Script**: Late Neo-Assyrian') + expect(markdown).toContain('**Period**: Late Neo-Assyrian') }) it('handle script formatting with uncertainty', () => { @@ -158,7 +158,7 @@ describe('DossierRecord', () => { }, }) const markdown = record.toMarkdownString() - expect(markdown).toContain('**Script**: Neo-Assyrian (?)') + expect(markdown).toContain('**Period**: Neo-Assyrian (?)') }) }) }) diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts index 7c907fe69..3746dd369 100644 --- a/src/dossiers/domain/DossierRecord.ts +++ b/src/dossiers/domain/DossierRecord.ts @@ -1,5 +1,5 @@ import { immerable } from 'immer' -import { Provenance } from 'corpus/domain/provenance' +import { Provenance, Provenances } from 'corpus/domain/provenance' import { Script, ScriptDto } from 'fragmentarium/domain/fragment' import Citation from 'bibliography/domain/Citation' import Reference from 'bibliography/domain/Reference' @@ -15,7 +15,7 @@ interface DossierRecordDto { readonly yearRangeFrom?: number readonly yearRangeTo?: number readonly relatedKings?: number[] - readonly provenance?: Provenance + readonly provenance?: string readonly script?: ScriptDto readonly references?: ReferenceDto[] } @@ -50,7 +50,7 @@ export default class DossierRecord { this.yearRangeFrom = yearRangeFrom this.yearRangeTo = yearRangeTo this.relatedKings = relatedKings - this.provenance = provenance + this.provenance = provenance ? Provenances[provenance] : null this.script = script && createScript(script) this.references = references.map((referenceDto) => createReference(referenceDto) @@ -60,14 +60,14 @@ export default class DossierRecord { toMarkdownString(): string { const parts = [ { name: 'Description', value: this.description }, - { name: 'Period', value: this.yearsToMarkdownString() }, + { name: 'Provenance', value: this.provenance?.name }, + { name: 'Period', value: this.scriptToMarkdownString() }, { name: 'Related Kings', value: this.relatedKings.length > 0 ? this.relatedKings.join(', ') : null, }, - { name: 'Provenance', value: this.provenance?.name }, - { name: 'Script', value: this.scriptToMarkdownString() }, + { name: 'Date', value: this.yearsToMarkdownString() }, { name: 'Bibliography', value: this.references @@ -75,10 +75,6 @@ export default class DossierRecord { .join('; '), }, ] - console.log(this.references) - console.log( - this.references.map((reference) => Citation.for(reference).getMarkdown()) - ) return parts .filter((part) => !!part.value) .map((part) => `**${part.name}**: ${part.value}`) diff --git a/src/dossiers/infrastructure/DossiersRepository.test.ts b/src/dossiers/infrastructure/DossiersRepository.test.ts index e73f1b0de..63b64063e 100644 --- a/src/dossiers/infrastructure/DossiersRepository.test.ts +++ b/src/dossiers/infrastructure/DossiersRepository.test.ts @@ -1,9 +1,7 @@ import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' import DossierRecord from 'dossiers/domain/DossierRecord' import ApiClient from 'http/ApiClient' -import { PeriodModifiers, Periods } from 'common/period' -import { Provenances } from 'corpus/domain/provenance' -import { referenceFactory } from 'test-support/bibliography-fixtures' +import { referenceDtoFactory } from 'test-support/bibliography-fixtures' jest.mock('http/ApiClient') jest.mock('dossiers/application/DossiersService') @@ -12,19 +10,19 @@ const apiClient = new (ApiClient as jest.Mock>)() const dossiersRepository = new DossiersRepository(apiClient) const resultStub = { - id: 'test', + _id: 'test', description: 'some description', isApproximateDate: true, yearRangeFrom: -500, yearRangeTo: -470, relatedKings: [10.2, 11], - provenance: Provenances.Assyria, + provenance: 'Assyria', script: { - period: Periods['Neo-Assyrian'], - periodModifier: PeriodModifiers.None, + period: 'Neo-Assyrian', + periodModifier: 'None', uncertain: false, }, - references: [referenceFactory.build()], + references: [referenceDtoFactory.build()], } const query = ['test', 'test2'] diff --git a/src/dossiers/ui/DossiersDisplay.test.tsx b/src/dossiers/ui/DossiersDisplay.test.tsx index 4dd2c6b8f..0c4531387 100644 --- a/src/dossiers/ui/DossiersDisplay.test.tsx +++ b/src/dossiers/ui/DossiersDisplay.test.tsx @@ -8,7 +8,6 @@ import FragmentDossierRecordsDisplay, { DossierRecordDisplay, DossierRecordsListDisplay, } from './DossiersDisplay' -import { Provenances } from 'corpus/domain/provenance' import { fragmentFactory } from 'test-support/fragment-fixtures' import { referenceDtoFactory } from 'test-support/bibliography-fixtures' import { act } from 'react-dom/test-utils' @@ -28,7 +27,7 @@ const mockRecordDto = { yearRangeFrom: -500, yearRangeTo: -470, relatedKings: [10.2, 11], - provenance: Provenances['Assyria'], + provenance: 'Assyria', script: { period: 'Neo-Assyrian', periodModifier: 'None', From c0f17f3342b92952f415b66624df2ab15161e5db Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 14 Jan 2025 12:36:52 +0000 Subject: [PATCH 10/14] Fix type issues & tests --- src/dossiers/application/DossiersService.test.ts | 10 ++++------ src/dossiers/domain/DossierRecord.ts | 4 +++- src/fragmentarium/domain/Fragment.test.ts | 1 + .../ui/fragment/CuneiformFragment.test.tsx | 3 +++ src/fragmentarium/ui/fragment/FragmentView.test.tsx | 3 +++ src/router/sitemap.test.tsx | 5 +++++ 6 files changed, 19 insertions(+), 7 deletions(-) diff --git a/src/dossiers/application/DossiersService.test.ts b/src/dossiers/application/DossiersService.test.ts index 7cbfebb7f..99a7b4fc8 100644 --- a/src/dossiers/application/DossiersService.test.ts +++ b/src/dossiers/application/DossiersService.test.ts @@ -3,8 +3,6 @@ import DossiersRepository from 'dossiers/infrastructure/DossiersRepository' import DossierRecord from 'dossiers/domain/DossierRecord' import DossiersService from 'dossiers/application/DossiersService' import { stringify } from 'query-string' -import { Provenances } from 'corpus/domain/provenance' -import { PeriodModifiers, Periods } from 'common/period' import { referenceFactory } from 'test-support/bibliography-fixtures' jest.mock('dossiers/infrastructure/DossiersRepository') @@ -13,16 +11,16 @@ const dossiersRepository = new (DossiersRepository as jest.Mock)() const dossiersService = new DossiersService(dossiersRepository) const resultStub = { - id: 'test', + _id: 'test', description: 'some desciption', isApproximateDate: true, yearRangeFrom: -500, yearRangeTo: -470, relatedKings: [10.2, 11], - provenance: Provenances.Assyria, + provenance: 'Assyria', script: { - period: Periods['Neo-Assyrian'], - periodModifier: PeriodModifiers.None, + period: 'Neo-Assyrian', + periodModifier: 'None', uncertain: false, }, references: [referenceFactory.build()], diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts index 3746dd369..d56e835bc 100644 --- a/src/dossiers/domain/DossierRecord.ts +++ b/src/dossiers/domain/DossierRecord.ts @@ -71,7 +71,9 @@ export default class DossierRecord { { name: 'Bibliography', value: this.references - .map((reference) => Citation.for(reference).getMarkdown()) + .map((reference) => { + return Citation.for(reference).getMarkdown() + }) .join('; '), }, ] diff --git a/src/fragmentarium/domain/Fragment.test.ts b/src/fragmentarium/domain/Fragment.test.ts index 6ef1f0635..f4f885ad1 100644 --- a/src/fragmentarium/domain/Fragment.test.ts +++ b/src/fragmentarium/domain/Fragment.test.ts @@ -136,6 +136,7 @@ const config: Parameters[0] = { }, projects: [], authorizedScopes: ['CAIC'], + dossiers: [], } describe('Fragment', () => { diff --git a/src/fragmentarium/ui/fragment/CuneiformFragment.test.tsx b/src/fragmentarium/ui/fragment/CuneiformFragment.test.tsx index 9a3ab2e9f..d4f030e65 100644 --- a/src/fragmentarium/ui/fragment/CuneiformFragment.test.tsx +++ b/src/fragmentarium/ui/fragment/CuneiformFragment.test.tsx @@ -18,6 +18,7 @@ import { folioPagerFactory } from 'test-support/fragment-data-fixtures' import { Fragment } from 'fragmentarium/domain/fragment' import { FindspotService } from 'fragmentarium/application/FindspotService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' +import DossiersService from 'dossiers/application/DossiersService' jest.mock('dictionary/application/WordService') jest.mock('fragmentarium/application/FindspotService') @@ -33,6 +34,7 @@ let fragmentSearchService: jest.Mocked let wordService: jest.Mocked let findspotService: jest.Mocked let afoRegisterService: jest.Mocked +let dossiersService: jest.Mocked let session: jest.Mocked let updatedFragment: Fragment @@ -101,6 +103,7 @@ beforeEach(async () => { wordService={wordService} findspotService={findspotService} afoRegisterService={afoRegisterService} + dossiersService={dossiersService} activeLine="" /> diff --git a/src/fragmentarium/ui/fragment/FragmentView.test.tsx b/src/fragmentarium/ui/fragment/FragmentView.test.tsx index 19da8ca6e..48d9bc338 100644 --- a/src/fragmentarium/ui/fragment/FragmentView.test.tsx +++ b/src/fragmentarium/ui/fragment/FragmentView.test.tsx @@ -26,6 +26,7 @@ import { helmetContext } from 'router/head' import { HelmetProvider } from 'react-helmet-async' import { FindspotService } from 'fragmentarium/application/FindspotService' import AfoRegisterService from 'afo-register/application/AfoRegisterService' +import DossiersService from 'dossiers/application/DossiersService' jest.mock('dictionary/application/WordService') jest.mock('fragmentarium/application/FindspotService') @@ -41,6 +42,7 @@ let fragmentSearchService: jest.Mocked let wordService: jest.Mocked let findspotService: jest.Mocked let afoRegisterService: jest.Mocked +let dossiersService: jest.Mocked let session let container: HTMLElement @@ -65,6 +67,7 @@ function renderFragmentView( wordService={wordService} findspotService={findspotService} afoRegisterService={afoRegisterService} + dossiersService={dossiersService} activeLine="" session={session} /> diff --git a/src/router/sitemap.test.tsx b/src/router/sitemap.test.tsx index 46126fa15..980e7e653 100644 --- a/src/router/sitemap.test.tsx +++ b/src/router/sitemap.test.tsx @@ -14,6 +14,7 @@ import Bluebird from 'bluebird' import { Services } from './router' import { saveAs } from 'file-saver' import { FindspotService } from 'fragmentarium/application/FindspotService' +import DossiersService from 'dossiers/application/DossiersService' jest.mock('file-saver', () => ({ saveAs: jest.fn() })) @@ -56,6 +57,9 @@ beforeEach(() => { const afoRegisterService = new (AfoRegisterService as jest.Mock< jest.Mocked >)() + const dossiersService = new (DossiersService as jest.Mock< + jest.Mocked + >)() signService.listAllSigns.mockReturnValue(Bluebird.resolve(['a2'])) bibliographyService.listAllBibliography.mockReturnValue( @@ -83,6 +87,7 @@ beforeEach(() => { markupService: markupService, cachedMarkupService: cachedMarkupService, afoRegisterService: afoRegisterService, + dossiersService: dossiersService, } }) From 438ba3cf69c14a376867e2c71672d9266f389027 Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Wed, 15 Jan 2025 22:34:54 +0000 Subject: [PATCH 11/14] Update display --- src/dossiers/ui/DossiersDisplay.sass | 10 ++++------ src/dossiers/ui/DossiersDisplay.tsx | 9 +++++++-- 2 files changed, 11 insertions(+), 8 deletions(-) diff --git a/src/dossiers/ui/DossiersDisplay.sass b/src/dossiers/ui/DossiersDisplay.sass index 67b8d3311..fae6bff16 100644 --- a/src/dossiers/ui/DossiersDisplay.sass +++ b/src/dossiers/ui/DossiersDisplay.sass @@ -1,13 +1,11 @@ .dossier-records__item - cursor: pointer margin-right: 8px + +.dossier-records__item .dossier-name, .dossier-records__item__active .dossier-name + cursor: pointer text-decoration: underline text-decoration-style: dotted - &__active - text-decoration: underline - text-decoration-style: dotted - .reference-popover__popover opacity: 0 transition: opacity 0.4s linear @@ -17,4 +15,4 @@ opacity: 1 .dossier-record p - margin-block-end: 0 \ No newline at end of file + margin-block-end: 0 diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index 63d2a0ffd..755e732c6 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -42,10 +42,15 @@ function DossierInfoSpan({ setActiveDossier(isActive ? null : index)} ref={targetRef} > - {`Dossier: ${recordId}`} + Dossier: + setActiveDossier(isActive ? null : index)} + > + {recordId} + ) } From ca2a5d8ea38f033e223299c98a87256d7706d29f Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Thu, 16 Jan 2025 18:32:43 +0000 Subject: [PATCH 12/14] Add dossiers to search, update display & tests --- src/dossiers/ui/DossiersDisplay.test.tsx | 8 +- src/fragmentarium/ui/SearchForm.tsx | 2 + .../ui/front-page/Fragmentarium.tsx | 8 +- .../ui/front-page/LatestTransliterations.tsx | 15 +- .../ui/search/FragmentariumSearch.tsx | 5 + .../ui/search/FragmentariumSearchResult.tsx | 193 ++--------------- .../FragmentariumSearchResultComponents.tsx | 195 ++++++++++++++++++ src/research-projects/subpages/caic/Home.tsx | 4 + .../subpages/caic/Search.tsx | 4 + src/router/fragmentariumRoutes.tsx | 2 + src/router/researchProjectRoutes.tsx | 5 + 11 files changed, 253 insertions(+), 188 deletions(-) create mode 100644 src/fragmentarium/ui/search/FragmentariumSearchResultComponents.tsx diff --git a/src/dossiers/ui/DossiersDisplay.test.tsx b/src/dossiers/ui/DossiersDisplay.test.tsx index 0c4531387..2236895bd 100644 --- a/src/dossiers/ui/DossiersDisplay.test.tsx +++ b/src/dossiers/ui/DossiersDisplay.test.tsx @@ -59,11 +59,13 @@ describe('DossierRecordsListDisplay', () => { render() await act(async () => { - const dossierSpan = screen.getByText('Dossier: test') + const dossierSpan = screen.getByText('test') userEvent.click(dossierSpan) }) - expect(screen.getAllByText(/Dossier: test/)).toHaveLength(records.length) + expect(screen.getAllByText(/test/, { selector: 'span' })).toHaveLength( + records.length + ) expect(screen.getByText(/ca. 500 BCE - 470 BCE/)).toBeInTheDocument() }) }) @@ -86,7 +88,7 @@ describe('withData HOC integration', () => { ) }) await act(async () => { - const dossierSpan = screen.getByText(/Dossier: test/) + const dossierSpan = screen.getByText(/test/) userEvent.click(dossierSpan) }) await screen.findByText(/Test description/) diff --git a/src/fragmentarium/ui/SearchForm.tsx b/src/fragmentarium/ui/SearchForm.tsx index 9a8243b53..28cb4fd57 100644 --- a/src/fragmentarium/ui/SearchForm.tsx +++ b/src/fragmentarium/ui/SearchForm.tsx @@ -42,6 +42,7 @@ import BibliographyService from 'bibliography/application/BibliographyService' import { ResearchProjects } from 'research-projects/researchProject' import './SearchForm.sass' import ProvenanceSearchForm from './ProvenanceSearchForm' +import DossiersService from 'dossiers/application/DossiersService' interface State { number: string | null @@ -64,6 +65,7 @@ interface State { export type SearchFormProps = { fragmentSearchService: FragmentSearchService fragmentService: FragmentService + dossiersService: DossiersService bibliographyService: BibliographyService fragmentQuery?: FragmentQuery wordService: WordService diff --git a/src/fragmentarium/ui/front-page/Fragmentarium.tsx b/src/fragmentarium/ui/front-page/Fragmentarium.tsx index 7982fbe64..9abdf1587 100644 --- a/src/fragmentarium/ui/front-page/Fragmentarium.tsx +++ b/src/fragmentarium/ui/front-page/Fragmentarium.tsx @@ -16,10 +16,12 @@ function Fragmentarium({ fragmentService, bibliographyService, fragmentSearchService, + dossiersService, wordService, }: Pick< SearchFormProps, | 'fragmentService' + | 'dossiersService' | 'fragmentSearchService' | 'bibliographyService' | 'fragmentQuery' @@ -36,6 +38,7 @@ function Fragmentarium({ @@ -51,7 +54,10 @@ function Fragmentarium({ {session.isAllowedToReadFragments() && ( - + )} diff --git a/src/fragmentarium/ui/front-page/LatestTransliterations.tsx b/src/fragmentarium/ui/front-page/LatestTransliterations.tsx index 99e8b3ada..e3590eb52 100644 --- a/src/fragmentarium/ui/front-page/LatestTransliterations.tsx +++ b/src/fragmentarium/ui/front-page/LatestTransliterations.tsx @@ -1,15 +1,18 @@ import React from 'react' import withData from 'http/withData' import FragmentService from 'fragmentarium/application/FragmentService' +import DossiersService from 'dossiers/application/DossiersService' import { QueryResult } from 'query/QueryResult' -import { FragmentLines } from '../search/FragmentariumSearchResult' +import { FragmentLines } from 'fragmentarium/ui/search/FragmentariumSearchResultComponents' function LatestTransliterations({ data, fragmentService, + dossiersService, }: { data: QueryResult fragmentService: FragmentService + dossiersService: DossiersService }) { return (
        @@ -18,6 +21,7 @@ function LatestTransliterations({ ( - ({ data, fragmentService }): JSX.Element => { + ({ data, fragmentService, dossiersService }): JSX.Element => { return ( - + ) }, (props) => props.fragmentService.queryLatest() diff --git a/src/fragmentarium/ui/search/FragmentariumSearch.tsx b/src/fragmentarium/ui/search/FragmentariumSearch.tsx index f7bb8bedc..8fc03fc8c 100644 --- a/src/fragmentarium/ui/search/FragmentariumSearch.tsx +++ b/src/fragmentarium/ui/search/FragmentariumSearch.tsx @@ -17,12 +17,14 @@ import { CorpusSearchResult } from 'corpus/ui/search/CorpusSearchResult' import TextService from 'corpus/application/TextService' import { Col, Row, Tab, Tabs } from 'react-bootstrap' import { CorpusQuery } from 'query/CorpusQuery' +import DossiersService from 'dossiers/application/DossiersService' type Props = Pick< SearchFormProps, 'fragmentService' | 'fragmentSearchService' | 'bibliographyService' > & { fragmentQuery: FragmentQuery + dossiersService: DossiersService wordService: WordService textService: TextService activeTab: string @@ -39,6 +41,7 @@ function hasNonDefaultValues(query: FragmentQuery | CorpusQuery) { function FragmentariumSearch({ fragmentService, + dossiersService, fragmentSearchService, bibliographyService, fragmentQuery, @@ -68,6 +71,7 @@ function FragmentariumSearch({ diff --git a/src/fragmentarium/ui/search/FragmentariumSearchResult.tsx b/src/fragmentarium/ui/search/FragmentariumSearchResult.tsx index c62ab74c4..e7dd16258 100644 --- a/src/fragmentarium/ui/search/FragmentariumSearchResult.tsx +++ b/src/fragmentarium/ui/search/FragmentariumSearchResult.tsx @@ -1,36 +1,27 @@ import React, { useState } from 'react' import _ from 'lodash' -import FragmentService, { - ThumbnailBlob, -} from 'fragmentarium/application/FragmentService' +import FragmentService from 'fragmentarium/application/FragmentService' import withData from 'http/withData' import { QueryItem, QueryResult } from 'query/QueryResult' -import { Col, Container, Row } from 'react-bootstrap' -import { Fragment } from 'fragmentarium/domain/fragment' +import { Col, Row } from 'react-bootstrap' import { FragmentQuery } from 'query/FragmentQuery' -import { RenderFragmentLines } from 'dictionary/ui/search/FragmentLemmaLines' -import FragmentLink, { createFragmentUrl } from '../FragmentLink' -import { Genres } from 'fragmentarium/domain/Genres' -import ReferenceList from 'bibliography/ui/ReferenceList' import { linesToShow } from './FragmentariumSearch' import './FragmentariumSearchResult.sass' -import DateDisplay from 'chronology/ui/DateDisplay' import { stringify } from 'query-string' import { ResultPageButtons } from 'common/ResultPageButtons' -import { ProjectList } from '../info/ResearchProjects' -import { RecordList } from 'fragmentarium/ui/info/Record' -import { RecordEntry } from 'fragmentarium/domain/RecordEntry' -import ErrorBoundary from 'common/ErrorBoundary' -import { ThumbnailImage } from 'common/BlobImage' +import { FragmentLines } from './FragmentariumSearchResultComponents' +import DossiersService from 'dossiers/application/DossiersService' function ResultPages({ fragments, fragmentService, + dossiersService, linesToShow, queryLemmas, }: { fragments: readonly QueryItem[] fragmentService: FragmentService + dossiersService: DossiersService linesToShow: number queryLemmas?: readonly string[] }): JSX.Element { @@ -48,6 +39,7 @@ function ResultPages({ ) } -function GenresDisplay({ genres }: { genres: Genres }): JSX.Element { - return ( -
          - {genres.genres.map((genreItem) => { - const uncertain = genreItem.uncertain ? '(?)' : '' - return ( -
            - {`${genreItem.category.join(' ➝ ')} ${uncertain}`} -
          - ) - })} -
        - ) -} - -const FragmentThumbnail = withData< - { fragment: Fragment }, - { fragmentService: FragmentService }, - ThumbnailBlob ->( - ({ data, fragment }) => { - return data.blob ? ( - - ) : ( - <> - ) - }, - ({ fragment, fragmentService }) => - fragmentService.findThumbnail(fragment, 'small') -) - -function TransliterationRecord({ - record, - className, -}: { - record: readonly RecordEntry[] - className?: string -}): JSX.Element { - const latestRecord = _(record) - .filter((record) => record.type === 'Transliteration') - .first() - return ( - - ) -} -function ResponsiveCol({ ...props }): JSX.Element { - return -} - -export const FragmentLines = withData< +export const SearchResult = withData< { - queryLemmas?: readonly string[] - queryItem: QueryItem - linesToShow: number - includeLatestRecord?: boolean fragmentService: FragmentService + dossiersService: DossiersService + fragmentQuery: FragmentQuery }, - { - active?: number - }, - Fragment ->( - ({ - data: fragment, - queryLemmas, - queryItem, - linesToShow, - includeLatestRecord, - fragmentService, - }): JSX.Element => { - const script = fragment.script.period.abbreviation - ? ` (${fragment.script.period.abbreviation})` - : '' - return ( - - - -

        - - {fragment.number} - - {script} -

        -
        - -

        - {fragment.accession && 'Accession no.: '} - {fragment.accession} -

        -

        - {fragment.archaeology?.excavationNumber && 'Excavation no.: '} - {fragment.archaeology?.excavationNumber} -

        -

        - {fragment.archaeology?.site?.name && 'Provenance: '} - {fragment.archaeology?.site?.name} -

        -
        -
        - -
        - - - - - {includeLatestRecord && ( - - )} - -
        - {fragment?.date && ( - - - - - - )} - - - - - - - - - - - - {fragment.hasPhoto && ( - - )} - - - -
        -
        - ) - }, - ({ fragmentService, queryItem, linesToShow }) => { - const excludeLines = _.isEmpty(queryItem.matchingLines) - return fragmentService.find( - queryItem.museumNumber, - _.take(queryItem.matchingLines, linesToShow), - excludeLines - ) - }, - { - watch: ({ active }) => [active], - } -) - -export const SearchResult = withData< - { fragmentService: FragmentService; fragmentQuery: FragmentQuery }, unknown, QueryResult >( - ({ data, fragmentService, fragmentQuery }): JSX.Element => { + ({ data, fragmentService, dossiersService, fragmentQuery }): JSX.Element => { const fragmentCount = data.items.length const isLineQuery = fragmentQuery.lemmas || fragmentQuery.transliteration const lineCountInfo = `${data.matchCountTotal.toLocaleString()} line${ @@ -271,6 +101,7 @@ export const SearchResult = withData< + {genres.genres.map((genreItem) => { + const uncertain = genreItem.uncertain ? '(?)' : '' + return ( +
          + {`${genreItem.category.join(' ➝ ')} ${uncertain}`} +
        + ) + })} + + ) +} + +const FragmentThumbnail = withData< + { fragment: Fragment }, + { fragmentService: FragmentService }, + ThumbnailBlob +>( + ({ data, fragment }) => { + return data.blob ? ( + + ) : ( + <> + ) + }, + ({ fragment, fragmentService }) => + fragmentService.findThumbnail(fragment, 'small') +) + +function TransliterationRecord({ + record, + className, +}: { + record: readonly RecordEntry[] + className?: string +}): JSX.Element { + const latestRecord = _(record) + .filter((record) => record.type === 'Transliteration') + .first() + return ( + + ) +} + +function ResponsiveCol({ ...props }): JSX.Element { + return +} + +export const FragmentLines = withData< + { + queryLemmas?: readonly string[] + queryItem: QueryItem + linesToShow: number + includeLatestRecord?: boolean + fragmentService: FragmentService + dossiersService: DossiersService + }, + { + active?: number + }, + Fragment +>( + ({ + data: fragment, + queryLemmas, + queryItem, + linesToShow, + includeLatestRecord, + fragmentService, + dossiersService, + }): JSX.Element => { + const script = fragment.script.period.abbreviation + ? ` (${fragment.script.period.abbreviation})` + : '' + return ( + + + +

        + + {fragment.number} + + {script} +

        +
        + +

        + {fragment.accession && 'Accession no.: '} + {fragment.accession} +

        +

        + {fragment.archaeology?.excavationNumber && 'Excavation no.: '} + {fragment.archaeology?.excavationNumber} +

        +

        + {fragment.archaeology?.site?.name && 'Provenance: '} + {fragment.archaeology?.site?.name} +

        + +
        +
        + +
        + + + + + {includeLatestRecord && ( + + )} + +
        + {fragment?.date && ( + + + + + + )} + + + + + + + + + + + + {fragment.hasPhoto && ( + + )} + + + +
        +
        + ) + }, + ({ fragmentService, queryItem, linesToShow }) => { + const excludeLines = _.isEmpty(queryItem.matchingLines) + return fragmentService.find( + queryItem.museumNumber, + _.take(queryItem.matchingLines, linesToShow), + excludeLines + ) + }, + { + watch: ({ active }) => [active], + } +) diff --git a/src/research-projects/subpages/caic/Home.tsx b/src/research-projects/subpages/caic/Home.tsx index 2d1e3b2c1..7d5649f2e 100644 --- a/src/research-projects/subpages/caic/Home.tsx +++ b/src/research-projects/subpages/caic/Home.tsx @@ -10,6 +10,7 @@ export default function Home({ fragmentSearchService, bibliographyService, wordService, + dossiersService, fragmentQuery, }: Pick< SearchFormProps, @@ -17,6 +18,7 @@ export default function Home({ | 'fragmentSearchService' | 'bibliographyService' | 'wordService' + | 'dossiersService' | 'fragmentQuery' >): JSX.Element { return ( @@ -41,6 +43,7 @@ export default function Home({ )} diff --git a/src/research-projects/subpages/caic/Search.tsx b/src/research-projects/subpages/caic/Search.tsx index 1d213e393..b48c6d3ef 100644 --- a/src/research-projects/subpages/caic/Search.tsx +++ b/src/research-projects/subpages/caic/Search.tsx @@ -9,6 +9,7 @@ export default function Search({ fragmentSearchService, bibliographyService, wordService, + dossiersService, fragmentQuery, }: Pick< SearchFormProps, @@ -16,6 +17,7 @@ export default function Search({ | 'fragmentSearchService' | 'bibliographyService' | 'wordService' + | 'dossiersService' | 'fragmentQuery' >): JSX.Element { return ( @@ -27,6 +29,7 @@ export default function Search({ fragmentService={fragmentService} wordService={wordService} bibliographyService={bibliographyService} + dossiersService={dossiersService} fragmentQuery={fragmentQuery} project={'CAIC'} /> @@ -35,6 +38,7 @@ export default function Search({ {fragmentQuery && ( )} diff --git a/src/router/fragmentariumRoutes.tsx b/src/router/fragmentariumRoutes.tsx index 0a9aeea14..e0f9d7b4a 100644 --- a/src/router/fragmentariumRoutes.tsx +++ b/src/router/fragmentariumRoutes.tsx @@ -89,6 +89,7 @@ export default function FragmentariumRoutes({ bibliographyService={bibliographyService} wordService={wordService} textService={textService} + dossiersService={dossiersService} activeTab={_.trimStart(location.hash, '#')} /> @@ -174,6 +175,7 @@ export default function FragmentariumRoutes({ wordService={wordService} fragmentService={fragmentService} fragmentSearchService={fragmentSearchService} + dossiersService={dossiersService} bibliographyService={bibliographyService} /> diff --git a/src/router/researchProjectRoutes.tsx b/src/router/researchProjectRoutes.tsx index 79459ef7b..5fa6e20ea 100644 --- a/src/router/researchProjectRoutes.tsx +++ b/src/router/researchProjectRoutes.tsx @@ -11,6 +11,7 @@ import FragmentService from 'fragmentarium/application/FragmentService' import ResearchProjectsOverview from 'research-projects/ResearchProjectsOverview' import CaicHome from 'research-projects/subpages/caic/Home' import CaicSearch from 'research-projects/subpages/caic/Search' +import DossiersService from 'dossiers/application/DossiersService' export default function ResearchProjectRoutes({ sitemap, @@ -18,12 +19,14 @@ export default function ResearchProjectRoutes({ fragmentSearchService, wordService, bibliographyService, + dossiersService, }: { sitemap: boolean fragmentService: FragmentService fragmentSearchService: FragmentSearchService wordService: WordService bibliographyService: BibliographyService + dossiersService: DossiersService }): JSX.Element[] { return [ )} @@ -62,6 +66,7 @@ export default function ResearchProjectRoutes({ fragmentSearchService={fragmentSearchService} wordService={wordService} bibliographyService={bibliographyService} + dossiersService={dossiersService} fragmentQuery={{ ...parse(location.search), project: 'CAIC' }} /> From 7856cc3a4a8968027d69ca8426fe7b5f3075ecef Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Thu, 16 Jan 2025 18:38:35 +0000 Subject: [PATCH 13/14] Fix type issue --- .../ui/front-page/LatestTransliterations.test.tsx | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/src/fragmentarium/ui/front-page/LatestTransliterations.test.tsx b/src/fragmentarium/ui/front-page/LatestTransliterations.test.tsx index 556dce141..d58cef9b9 100644 --- a/src/fragmentarium/ui/front-page/LatestTransliterations.test.tsx +++ b/src/fragmentarium/ui/front-page/LatestTransliterations.test.tsx @@ -12,6 +12,7 @@ import { DictionaryContext } from 'dictionary/ui/dictionary-context' import SessionContext from 'auth/SessionContext' import MemorySession, { Session } from 'auth/Session' import { queryItemOf } from 'test-support/utils' +import DossiersService from 'dossiers/application/DossiersService' jest.mock('fragmentarium/application/FragmentService') jest.mock('dictionary/application/WordService') @@ -27,6 +28,9 @@ const fragmentService = new (FragmentService as jest.Mock< jest.Mocked >)() const wordService = new (WordService as jest.Mock>)() +const dossiersService = new (DossiersService as jest.Mock< + jest.Mocked +>)() beforeEach(async () => { session = new MemorySession(['read:fragments']) @@ -48,7 +52,10 @@ beforeEach(async () => { - + From 2f9f2003f4e7d36d135607d9c6de526371874c1f Mon Sep 17 00:00:00 2001 From: Ilya Khait Date: Tue, 21 Jan 2025 12:15:43 +0000 Subject: [PATCH 14/14] Add bibliography popups & update tests --- src/dossiers/domain/DossierRecord.ts | 16 +++++---- src/dossiers/ui/DossiersDisplay.test.tsx | 42 +++++++++++++++++++++++- src/dossiers/ui/DossiersDisplay.tsx | 40 +++++++++++++++++++--- 3 files changed, 85 insertions(+), 13 deletions(-) diff --git a/src/dossiers/domain/DossierRecord.ts b/src/dossiers/domain/DossierRecord.ts index d56e835bc..e4410fe55 100644 --- a/src/dossiers/domain/DossierRecord.ts +++ b/src/dossiers/domain/DossierRecord.ts @@ -57,7 +57,9 @@ export default class DossierRecord { ) } - toMarkdownString(): string { + toMarkdownString( + { bibliography }: { bibliography: boolean } = { bibliography: true } + ): string { const parts = [ { name: 'Description', value: this.description }, { name: 'Provenance', value: this.provenance?.name }, @@ -68,15 +70,15 @@ export default class DossierRecord { this.relatedKings.length > 0 ? this.relatedKings.join(', ') : null, }, { name: 'Date', value: this.yearsToMarkdownString() }, - { + ] + if (bibliography) { + parts.push({ name: 'Bibliography', value: this.references - .map((reference) => { - return Citation.for(reference).getMarkdown() - }) + .map((reference) => Citation.for(reference).getMarkdown()) .join('; '), - }, - ] + }) + } return parts .filter((part) => !!part.value) .map((part) => `**${part.name}**: ${part.value}`) diff --git a/src/dossiers/ui/DossiersDisplay.test.tsx b/src/dossiers/ui/DossiersDisplay.test.tsx index 2236895bd..9eca7170f 100644 --- a/src/dossiers/ui/DossiersDisplay.test.tsx +++ b/src/dossiers/ui/DossiersDisplay.test.tsx @@ -12,6 +12,7 @@ import { fragmentFactory } from 'test-support/fragment-fixtures' import { referenceDtoFactory } from 'test-support/bibliography-fixtures' import { act } from 'react-dom/test-utils' import userEvent from '@testing-library/user-event' +import Citation from 'bibliography/domain/Citation' jest.mock('common/MarkdownAndHtmlToHtml', () => ({ __esModule: true, @@ -43,6 +44,30 @@ describe('DossierRecordDisplay', () => { render() expect(screen.getByText(/Test description/)).toBeInTheDocument() }) + + it('renders bibliography references and handles popups', async () => { + render() + mockRecord.references.forEach((reference) => { + const referenceMarkdown = Citation.for(reference).getMarkdown() + expect( + screen.getByText(new RegExp(referenceMarkdown, 'i')) + ).toBeInTheDocument() + }) + const firstReferenceMarkdown = Citation.for( + mockRecord.references[0] + ).getMarkdown() + const referenceElement = screen.getByText( + new RegExp(firstReferenceMarkdown, 'i') + ) + + await act(async () => { + userEvent.click(referenceElement) + }) + expect(await screen.findByRole('tooltip')).toBeInTheDocument() + expect( + screen.getByText(new RegExp(mockRecord.references[0].notes)) + ).toBeInTheDocument() + }) }) describe('DossierRecordsListDisplay', () => { @@ -51,7 +76,7 @@ describe('DossierRecordsListDisplay', () => { expect(screen.queryByRole('list')).not.toBeInTheDocument() }) - it('renders a list of records', async () => { + it('renders a list of records and handles bibliography popups', async () => { const records = [ mockRecord, new DossierRecord({ ...mockRecordDto, _id: 'test2' }), @@ -67,6 +92,21 @@ describe('DossierRecordsListDisplay', () => { records.length ) expect(screen.getByText(/ca. 500 BCE - 470 BCE/)).toBeInTheDocument() + const firstReferenceMarkdown = Citation.for( + mockRecord.references[0] + ).getMarkdown() + const referenceElement = screen.getByText( + new RegExp(firstReferenceMarkdown, 'i') + ) + + await act(async () => { + userEvent.click(referenceElement) + }) + + expect(await screen.findAllByRole('tooltip')).toHaveLength(2) + expect( + screen.getByText(new RegExp(mockRecord.references[0].notes)) + ).toBeInTheDocument() }) }) diff --git a/src/dossiers/ui/DossiersDisplay.tsx b/src/dossiers/ui/DossiersDisplay.tsx index 755e732c6..65f4abba5 100644 --- a/src/dossiers/ui/DossiersDisplay.tsx +++ b/src/dossiers/ui/DossiersDisplay.tsx @@ -8,6 +8,33 @@ import Bluebird from 'bluebird' import { Popover, Overlay } from 'react-bootstrap' import { Fragment } from 'fragmentarium/domain/fragment' import './DossiersDisplay.sass' +import ReferencePopover from 'bibliography/ui/referencePopover' +import Citation from 'bibliography/domain/Citation' +import InlineMarkdown from 'common/InlineMarkdown' +import Reference from 'bibliography/domain/Reference' + +function ReferencesDisplay({ + references, +}: { + references: Reference[] +}): JSX.Element { + const InlineMarkdownWithPopover = ReferencePopover( + ({ reference }: { reference: Reference }) => ( + + ) + ) + return ( + <> + Bibliography:{' '} + {references.map((reference, index) => ( + + {index > 0 && '; '} + + + ))} + + ) +} export function DossierRecordDisplay({ record, @@ -17,11 +44,14 @@ export function DossierRecordDisplay({ index: number }): JSX.Element { return ( - + <> + + + ) }