Allow changing genealogical symbols (#1382)

* Mudanças para resolução da issue #507, além de outros erros

* Alteração dos ícones de eventos nas páginas pessoais

* Contribution for the resolution of issue #507 - providing different genealogical ssymbols

* Fix package-lock.json out of sync with package.json

* Restore package-lock.json to match upstream, fixing missing yaml@2.9.0

* Restore package-lock.json to match upstream, fixing missing yaml@2.9.0

* Implementing recommended changes

* Addressing the latest comments by Copilot

* Resolving ESLint test fail

* Resolving issues (cards, etc.) post refactoring of the tree views

* Resolving failing unit test issue: objectDetail undefined in 'util.test.js'

* Resolving ESLint error

* Changes made due to recent review

* Retouching the translation of abbreviations

* Discarding out-of-scope changes to objectRender.js and timeline.test.js

* Revert tabindex changes
This commit is contained in:
Nazivon
2026-10-06 03:11:47 -03:00
committed by GitHub
parent 5f937ce13f
commit ed4c7ccb8e
24 changed files with 351 additions and 26 deletions
+3
View File
@@ -63,6 +63,9 @@
"Recently changed objects": "Recently changed objects",
"Select language": "Select language",
"Select theme": "Select theme",
"Genealogical symbols": "Genealogical symbols",
"Unicode symbols (∗ † ⚭ ⚮)": "Unicode symbols (∗ † ⚭ ⚮)",
"Text abbreviations (b. d. m. div.)": "Text abbreviations (b. d. m. div.)",
"Theme": "Theme",
"System": "System",
"Light": "Light",
+6
View File
@@ -196,6 +196,12 @@ export const sharedStyles = css`
fill: var(--grampsjs-body-font-color-40);
}
.event i {
color: var(--grampsjs-body-font-color-40);
font-size: 16px;
font-style: normal;
}
.event i svg {
height: 16px;
width: auto;
+2
View File
@@ -128,6 +128,8 @@ export class ChartCanvas {
drawChangedCards(people, {
getImageUrl: settings.getImageUrl,
nameDisplayFormat,
birthSymbol: settings.birthSymbol,
deathSymbol: settings.deathSymbol,
palette,
boxWidth,
boxHeight,
+19 -2
View File
@@ -5,6 +5,7 @@ import {
colorAddPersonButtons,
} from './addPersonButton.js'
import {chartPalette} from './palette.js'
import {SYMBOL_SETS, SYMBOL_SET_DEFAULT} from '../symbols.js'
// Pattern ids must be unique in the document, and the same person can be
// shown in several cards
@@ -43,6 +44,8 @@ export function appendPersonCard(
boxHeight = 90,
imgPadding = 10,
nameDisplayFormat = chartNameDisplayFormat.surnameThenGiven,
birthSymbol = SYMBOL_SETS[SYMBOL_SET_DEFAULT].birthSymbol,
deathSymbol = SYMBOL_SETS[SYMBOL_SET_DEFAULT].deathSymbol,
palette = chartPalette,
}
) {
@@ -87,8 +90,16 @@ export function appendPersonCard(
text: p => (surnameFirst ? p.name_given || '…' : p.name_surname || '…'),
weight: 500,
},
{show: p => p?.birth?.date, text: p => `*${p.birth.date}`, weight: 350},
{show: p => p?.death?.date, text: p => `†${p.death.date}`, weight: 350},
{
show: p => p?.birth?.date,
text: p => `${birthSymbol} ${p.birth.date}`,
weight: 350,
},
{
show: p => p?.death?.date,
text: p => `${deathSymbol} ${p.death.date}`,
weight: 350,
},
]
lines.forEach((line, i) => {
nodes
@@ -195,6 +206,8 @@ export function drawChangedCards(
{
getImageUrl = () => '',
nameDisplayFormat,
birthSymbol,
deathSymbol,
palette = chartPalette,
boxWidth,
boxHeight,
@@ -206,6 +219,8 @@ export function drawChangedCards(
person: d.person,
imageUrl: getImageUrl(d),
nameDisplayFormat,
birthSymbol,
deathSymbol,
palette,
}
const previous = cardInputs.get(this)
@@ -229,6 +244,8 @@ export function drawChangedCards(
boxWidth,
boxHeight,
nameDisplayFormat,
birthSymbol,
deathSymbol,
palette,
})
}
+6 -1
View File
@@ -4,6 +4,7 @@ import {classMap} from 'lit/directives/class-map.js'
import {fireEvent} from '../util.js'
import {renderPersonAvatar, renderPersonDates} from './personListUtils.js'
import {GrampsjsEditableList} from './GrampsjsEditableList.js'
import {getSymbols} from '../symbols.js'
import './GrampsjsFormChildRef.js'
import './GrampsjsFormNewChild.js'
@@ -69,7 +70,11 @@ export class GrampsjsChildren extends GrampsjsEditableList {
}
}}"
>
${p.name_given || ''} ${p.name_surname || ''} ${renderPersonDates(p)}
${p.name_given || ''} ${p.name_surname || ''}
${renderPersonDates(
p,
getSymbols(this.appState.settings, s => this._(s))
)}
${hasNonBirthRel
? html`<span slot="supporting-text">${relText}</span>`
: ''}
+5 -1
View File
@@ -4,6 +4,7 @@ import {GrampsjsConnectedComponent} from './GrampsjsConnectedComponent.js'
import {fireEvent} from '../util.js'
import {renderPersonAvatar, renderPersonDates} from './personListUtils.js'
import {personListItemStyles} from '../SharedStyles.js'
import {getSymbols} from '../symbols.js'
import '@material/web/list/list.js'
import '@material/web/list/list-item.js'
@@ -85,7 +86,10 @@ export class GrampsjsConnectedParents extends GrampsjsConnectedComponent {
})}"
>
${personProfile.name_given || ''} ${personProfile.name_surname || ''}
${renderPersonDates(personProfile, {showAge: false})}
${renderPersonDates(personProfile, {
showAge: false,
...getSymbols(this.appState.settings, s => this._(s)),
})}
${renderPersonAvatar(extPerson, personProfile.sex)}
</md-list-item>
`
+14 -4
View File
@@ -11,6 +11,7 @@ import './GrampsjsFormEditFamily.js'
import './GrampsjsFormNewPerson.js'
import './GrampsjsFormPersonRef.js'
import {GrampsjsObject} from './GrampsjsObject.js'
import {getSymbols} from '../symbols.js'
export class GrampsjsFamily extends GrampsjsObject {
static get styles() {
@@ -97,6 +98,10 @@ export class GrampsjsFamily extends GrampsjsObject {
const divorce = this.data?.profile?.divorce
const hasMarriage = marriage?.date || marriage?.place
const hasDivorce = divorce && Object.keys(divorce).length > 0
const {marriageSymbol, divorceSymbol} = getSymbols(
this.appState.settings,
s => this._(s)
)
if (!relType && !hasMarriage && !hasDivorce && !this.edit) {
return ''
}
@@ -109,7 +114,7 @@ export class GrampsjsFamily extends GrampsjsObject {
${relType || ''}
${hasMarriage
? html`<span class="parent-dates">
<span class="sym">⚭</span>
<span class="sym">${marriageSymbol}</span>
${marriage.date || ''}
${marriage.place
? `${this._('in')} ${
@@ -120,7 +125,7 @@ export class GrampsjsFamily extends GrampsjsObject {
: ''}
${hasDivorce
? html`<span class="parent-dates">
<span class="sym">⚮</span>
<span class="sym">${divorceSymbol}</span>
${divorce.date || ''}
${divorce.place ? `${this._('in')} ${divorce.place}` : ''}
</span>`
@@ -153,6 +158,9 @@ export class GrampsjsFamily extends GrampsjsObject {
const hasProfile = Object.keys(profile ?? {}).length > 0
const birthDate = profile?.birth?.date || ''
const deathDate = profile?.death?.date || ''
const {birthSymbol, deathSymbol} = getSymbols(this.appState.settings, s =>
this._(s)
)
return html`
<dl>
@@ -170,11 +178,13 @@ export class GrampsjsFamily extends GrampsjsObject {
${birthDate || deathDate
? html`<span class="parent-dates">
${birthDate
? html`<span class="sym">∗</span> ${birthDate}`
? html`<span class="sym">${birthSymbol}</span>
${birthDate}`
: ''}
${birthDate && deathDate ? ' ' : ''}
${deathDate
? html`<span class="sym">†</span> ${deathDate}`
? html`<span class="sym">${deathSymbol}</span>
${deathDate}`
: ''}
</span>`
: ''}`
+2 -1
View File
@@ -525,7 +525,8 @@ class GrampsjsMapSearchbox extends GrampsjsAppStateMixin(LitElement) {
supportingText: objectDetail(
TYPE_PERSON,
obj.object,
this.appState.i18n.strings
this.appState.i18n.strings,
this.appState.settings
).trim(),
iconPath: mdiAccount,
}
+5 -3
View File
@@ -11,7 +11,7 @@ import {
mdiMap,
} from '@mdi/js'
import {GrampsjsObject} from './GrampsjsObject.js'
import {asteriskIcon, crossIcon} from '../icons.js'
import {getSymbols} from '../symbols.js'
import './GrampsjsImg.js'
import './GrampsjsEditGender.js'
import './GrampsjsPersonRelationship.js'
@@ -124,9 +124,10 @@ export class GrampsjsPerson extends GrampsjsObject {
if (obj === undefined || Object.keys(obj).length === 0) {
return ''
}
const {birthSymbol} = getSymbols(this.appState.settings, s => this._(s))
return html`
<span class="event">
<i>${asteriskIcon}</i>
<i>${birthSymbol}</i>
${obj.date || ''} ${obj.place ? this._('in') : ''}
${obj.place_name || obj.place || ''}
</span>
@@ -138,9 +139,10 @@ export class GrampsjsPerson extends GrampsjsObject {
if (obj === undefined || Object.keys(obj).length === 0) {
return ''
}
const {deathSymbol} = getSymbols(this.appState.settings, s => this._(s))
return html`
<span class="event">
<i>${crossIcon}</i>
<i>${deathSymbol}</i>
${obj.date || ''} ${obj.place ? this._('in') : ''}
${obj.place_name || obj.place || ''}
</span>
+8 -2
View File
@@ -7,6 +7,7 @@ import './GrampsjsEvents.js'
import {GrampsjsConnectedComponent} from './GrampsjsConnectedComponent.js'
import {fireEvent, personProfileDisplayName} from '../util.js'
import {sharedStyles} from '../SharedStyles.js'
import {getSymbols} from '../symbols.js'
export class GrampsjsPersonBox extends GrampsjsConnectedComponent {
static get styles() {
@@ -94,17 +95,22 @@ export class GrampsjsPersonBox extends GrampsjsConnectedComponent {
const birthPlace = person.profile?.birth?.place_name || ''
const deathDate = person.profile?.death?.date || ''
const deathPlace = person.profile?.death?.place_name || ''
const {birthSymbol, deathSymbol} = getSymbols(this.appState.settings, s =>
this._(s)
)
return html`
<h2>${fullName || this.name || this._('Person')}</h2>
<div class="dates">
${birthDate || birthPlace
? html`<p>
∗ ${[birthDate, birthPlace].filter(Boolean).join(' · ')}
${birthSymbol}
${[birthDate, birthPlace].filter(Boolean).join(' · ')}
</p>`
: ''}
${deathDate || deathPlace
? html`<p>
† ${[deathDate, deathPlace].filter(Boolean).join(' · ')}
${deathSymbol}
${[deathDate, deathPlace].filter(Boolean).join(' · ')}
</p>`
: ''}
</div>
+2 -1
View File
@@ -48,7 +48,8 @@ export class GrampsjsReferences extends GrampsjsEditableList {
const detail = objectDetail(
type,
merged,
this.appState.i18n.strings
this.appState.i18n.strings,
this.appState.settings
).trim()
return html`
<md-list-item
@@ -5,6 +5,7 @@ import {RelationshipChart} from '../charts/RelationshipChart.js'
import {layoutRelationships} from '../charts/layout/relationshipLayout.js'
import {chartTransitionDuration, getImageUrl} from '../charts/util.js'
import {fireEvent} from '../util.js'
import {getSymbols} from '../symbols.js'
class GrampsjsRelationshipChart extends GrampsjsChartBase {
static get properties() {
@@ -57,6 +58,7 @@ class GrampsjsRelationshipChart extends GrampsjsChartBase {
this._chart.update(this._layout, {
getImageUrl: node => getImageUrl(node.person, 100),
nameDisplayFormat: this.nameDisplayFormat,
...getSymbols(this.appState.settings, s => this._(s)),
canEdit: this.canEdit,
duration: chartTransitionDuration(),
bboxWidth: this.containerWidth,
+2 -1
View File
@@ -169,7 +169,8 @@ export class GrampsjsSearchResultList extends GrampsjsAppStateMixin(
const detail = objectDetail(
obj.object_type,
obj.object,
this.appState.i18n.strings
this.appState.i18n.strings,
this.appState.settings
).trim()
if (detail?.length) {
return detail
+4 -1
View File
@@ -16,6 +16,7 @@ import {
getImageUrl,
} from '../charts/util.js'
import {fireEvent, menuSelectionHandler} from '../util.js'
import {getSymbols} from '../symbols.js'
import {personListItemStyles} from '../SharedStyles.js'
import {renderPersonAvatar, renderPersonDates} from './personListUtils.js'
@@ -139,6 +140,7 @@ class GrampsjsTreeChart extends GrampsjsChartBase {
bboxWidth: this.containerWidth,
bboxHeight: this.containerHeight,
nameDisplayFormat: this.nameDisplayFormat,
...getSymbols(this.appState.settings, s => this._(s)),
canEdit: this.canEdit,
duration: chartTransitionDuration(),
})
@@ -169,6 +171,7 @@ class GrampsjsTreeChart extends GrampsjsChartBase {
if (relatives.length === 0) {
return ''
}
const symbols = getSymbols(this.appState.settings, s => this._(s))
return html`
<md-menu
id="relatives-menu"
@@ -184,7 +187,7 @@ class GrampsjsTreeChart extends GrampsjsChartBase {
<div slot="headline">
${formatChartName(person.profile, this.nameDisplayFormat)}
</div>
${renderPersonDates(person.profile)}
${renderPersonDates(person.profile, symbols)}
</md-menu-item>
`
)}
+14 -3
View File
@@ -2,6 +2,7 @@ import {html} from 'lit'
import {mdiAccount} from '@mdi/js'
import {objectIconPath, sexColor} from '../util.js'
import {SYMBOL_SETS, SYMBOL_SET_DEFAULT} from '../symbols.js'
import './GrampsjsImg.js'
import './GrampsjsIcon.js'
@@ -32,7 +33,14 @@ export function renderPersonAvatar(extPerson, sex) {
></grampsjs-icon>`
}
export function renderPersonDates(profile, {showAge = true} = {}) {
export function renderPersonDates(
profile,
{
showAge = true,
birthSymbol = SYMBOL_SETS[SYMBOL_SET_DEFAULT].birthSymbol,
deathSymbol = SYMBOL_SETS[SYMBOL_SET_DEFAULT].deathSymbol,
} = {}
) {
const birthStr = profile?.birth?.date || ''
const deathStr = profile?.death?.date || ''
const ageStr =
@@ -41,9 +49,12 @@ export function renderPersonDates(profile, {showAge = true} = {}) {
: ''
if (!birthStr && !deathStr && !ageStr) return ''
return html`<span slot="supporting-text"
><span class="date-col">${birthStr ? `∗ ${birthStr}` : ''}</span
><span class="date-col"
>${deathStr ? `† ${deathStr}` : ''}${ageStr ? ` ${ageStr}` : ''}</span
>${birthStr ? `${birthSymbol} ${birthStr}` : ''}</span
><span class="date-col"
>${deathStr ? `${deathSymbol} ${deathStr}` : ''}${ageStr
? ` ${ageStr}`
: ''}</span
></span
>`
}
+4
View File
@@ -151,6 +151,7 @@ export const grampsStrings = [
'Birth year until which to include people',
'Birth year',
'Birth',
'birth abbreviation\u0004b.',
'Birthday and Anniversary Report',
'Blessing',
'Bold',
@@ -253,6 +254,7 @@ export const grampsStrings = [
'Database overview',
'Database Summary Report',
'Date',
'death abbreviation\u0004d.',
'de Villiers/Pama numbering',
'Death Date',
'Death Place',
@@ -289,6 +291,7 @@ export const grampsStrings = [
'District',
'Divorce Filing',
'Divorce',
'Divorce abbreviation\u0004div.',
'Do not include a title',
'Do not include any dates or places',
'Do not include any occupation',
@@ -510,6 +513,7 @@ export const grampsStrings = [
'Marriage place',
'Marriage Settlement',
'Marriage',
'marriage abbreviation\u0004m.',
'Married Name',
'Married',
'Maternal',
+57
View File
@@ -0,0 +1,57 @@
// src/symbols.js
export const SYMBOL_SET_DEFAULT = 'default'
export const SYMBOL_SET_TEXT = 'text'
export const SYMBOL_SETS = {
[SYMBOL_SET_DEFAULT]: {
birthSymbol: '∗',
deathSymbol: '†',
marriageSymbol: '⚭',
divorceSymbol: '⚮',
},
[SYMBOL_SET_TEXT]: {
birthSymbol: 'b.',
deathSymbol: 'd.',
marriageSymbol: 'm.',
divorceSymbol: 'div.',
},
}
// Used to populate the <select> in user settings.
// `label` is a plain English string - passed through `this._()`
// in the component, following the same pattern as `_treeViewLabel()`
// in GrampsjsViewSettingsUser.js
export const SYMBOL_SET_OPTIONS = [
{value: SYMBOL_SET_DEFAULT, label: 'Unicode symbols (∗ † ⚭ ⚮)'},
{value: SYMBOL_SET_TEXT, label: 'Text abbreviations (b. d. m. div.)'},
]
const SYMBOL_TRANSLATION_CONTEXTS = {
birthSymbol: 'birth abbreviation',
deathSymbol: 'death abbreviation',
marriageSymbol: 'marriage abbreviation',
divorceSymbol: 'Divorce abbreviation',
}
export function getSymbols(settings, translate = value => value) {
const symbols =
SYMBOL_SETS[settings?.symbolSet] ?? SYMBOL_SETS[SYMBOL_SET_DEFAULT]
return Object.fromEntries(
Object.entries(symbols).map(([key, value]) => {
const context =
settings?.symbolSet === SYMBOL_SET_TEXT
? SYMBOL_TRANSLATION_CONTEXTS[key]
: undefined
if (!context) return [key, value]
const translationKey = `${context}\u0004${value}`
const translated = translate(translationKey)
return [
key,
translated == null || translated === translationKey
? value
: translated,
]
})
)
}
+10 -4
View File
@@ -52,6 +52,7 @@ import {
import {renderIconSvg, ringsIconPath} from './icons.js'
import './components/GrampsjsIcon.js'
import {frontendLanguages} from './strings.js'
import {getSymbols} from './symbols.js'
dayjs.extend(relativeTime)
@@ -437,13 +438,18 @@ export function objectDescription(type, obj, strings) {
}
}
export function objectDetail(type, obj, strings) {
export function objectDetail(type, obj, strings, settings = {}) {
const symbols = getSymbols(settings, value => translate(strings, value))
switch (type) {
case 'person':
return `
${obj?.profile?.birth?.date ? `∗ ${obj.profile.birth.date}` : ''}${
obj?.profile?.birth?.place && obj?.profile?.birth?.date ? ', ' : ''
}${obj?.profile?.birth?.place_name || obj?.profile?.birth?.place || ''}
${
obj?.profile?.birth?.date
? `${symbols.birthSymbol} ${obj.profile.birth.date}`
: ''
}${obj?.profile?.birth?.place && obj?.profile?.birth?.date ? ', ' : ''}${
obj?.profile?.birth?.place_name || obj?.profile?.birth?.place || ''
}
`
// case 'family':
// return ''
+30
View File
@@ -26,6 +26,7 @@ import {
import {fireEvent} from '../util.js'
import {applyScheme, DEFAULT_PRIMARY, DEFAULT_SECONDARY} from '../theme.js'
import {DEFAULT_TREE_VIEW, TREE_VIEWS} from '../treeDefaults.js'
import {SYMBOL_SET_DEFAULT, SYMBOL_SET_OPTIONS} from '../symbols.js'
export class GrampsjsViewSettingsUser extends GrampsjsView {
static get styles() {
@@ -143,6 +144,8 @@ export class GrampsjsViewSettingsUser extends GrampsjsView {
${this.renderLangSelect()}
<h3>${this._('Select theme')}</h3>
${this.renderThemeSelect()}
<h3>${this._('Genealogical symbols')}</h3>
${this.renderSymbolsSelect()}
<h3>${this._('Family tree preferences')}</h3>
${this.renderTreePreferences()}
</grampsjs-collapsible-section>
@@ -256,6 +259,33 @@ export class GrampsjsViewSettingsUser extends GrampsjsView {
}
}
renderSymbolsSelect() {
const symbolSet = this.appState.settings.symbolSet ?? SYMBOL_SET_DEFAULT
return html`
<md-filled-select
id="select-symbols"
label="${this._('Genealogical symbols')}"
@change="${this._handleSymbolsSelected}"
>
${SYMBOL_SET_OPTIONS.map(
({value, label}) => html`
<md-select-option value="${value}" ?selected=${value === symbolSet}>
${this._(label)}
</md-select-option>
`
)}
</md-filled-select>
`
}
_handleSymbolsSelected(event) {
const candidate = event.target.value || SYMBOL_SET_DEFAULT
const symbolSet = SYMBOL_SET_OPTIONS.some(({value}) => value === candidate)
? candidate
: SYMBOL_SET_DEFAULT
this.appState.updateSettings({symbolSet})
}
renderTreePreferences() {
const defaultView =
this.appState.settings.treeDefaultView ?? DEFAULT_TREE_VIEW
+29 -2
View File
@@ -3,6 +3,7 @@ import {create} from 'd3-selection'
import {
appendPersonCard,
clearPersonCardInteraction,
drawChangedCards,
setPersonCardInteraction,
} from '../../src/charts/personCard.js'
import {chartPalette} from '../../src/charts/palette.js'
@@ -91,7 +92,7 @@ const click = node =>
describe('appendPersonCard', () => {
it('shows the surname first with placeholders for missing names', () => {
const cards = renderCards()
expect(texts(cards.full)).toEqual(['Berg,', 'Anna', '*1900', '†1980'])
expect(texts(cards.full)).toEqual(['Berg,', 'Anna', '∗ 1900', '† 1980'])
expect(texts(cards.noSurname)).toEqual(['…,', 'Carl'])
expect(texts(cards.noGiven)).toEqual(['Doe,', '…'])
expect(texts(cards.notFetched)).toEqual([])
@@ -101,10 +102,36 @@ describe('appendPersonCard', () => {
const cards = renderCards({
nameDisplayFormat: chartNameDisplayFormat.givenThenSurname,
})
expect(texts(cards.full)).toEqual(['Anna', 'Berg', '*1900', '†1980'])
expect(texts(cards.full)).toEqual(['Anna', 'Berg', '∗ 1900', '† 1980'])
expect(texts(cards.noSurname)).toEqual(['Carl', '…'])
})
it('uses configured birth and death symbols', () => {
expect(
texts(renderCards({birthSymbol: 'b.', deathSymbol: 'd.'}).full)
).toEqual(['Berg,', 'Anna', 'b. 1900', 'd. 1980'])
})
it('redraws cards when symbols change', () => {
const svg = create('svg')
const nodes = svg
.selectAll('g')
.data([{person: people.full}])
.join('g')
nodes.append('g').attr('class', 'person-card')
const draw = symbols =>
drawChangedCards(nodes, {
...symbols,
nameDisplayFormat: chartNameDisplayFormat.surnameThenGiven,
})
draw({birthSymbol: '*', deathSymbol: '†'})
draw({birthSymbol: 'b.', deathSymbol: 'd.'})
expect(texts(nodes.node())).toEqual(['Berg,', 'Anna', 'b. 1900', 'd. 1980'])
})
it('shortens names that do not fit', () => {
const [surname] = texts(renderCards().longName)
expect(surname.endsWith('…')).toBe(true)
+21
View File
@@ -0,0 +1,21 @@
import {describe, it, expect} from 'vitest'
import {renderPersonDates} from '../../src/components/personListUtils.js'
describe('renderPersonDates', () => {
it('uses the default symbols when none are passed', () => {
const profile = {birth: {date: '1990'}, death: {date: '2020'}}
const result = renderPersonDates(profile)
expect(result.values).toContain('∗ 1990')
expect(result.values[1]).toContain('† 2020')
})
it('uses custom symbols when passed', () => {
const profile = {birth: {date: '1990'}, death: {date: '2020'}}
const result = renderPersonDates(profile, {
birthSymbol: 'b.',
deathSymbol: 'd.',
})
expect(result.values).toContain('b. 1990')
expect(result.values[1]).toContain('d. 2020')
})
})
+23
View File
@@ -1,5 +1,6 @@
import {describe, it, expect, vi} from 'vitest'
import {GrampsjsViewSettingsUser} from '../../src/views/GrampsjsViewSettingsUser.js'
import {SYMBOL_SET_DEFAULT, SYMBOL_SET_TEXT} from '../../src/symbols.js'
const makeElement = ({
username,
@@ -127,3 +128,25 @@ describe('user profile settings', () => {
expect(element._fetchOwnUserDetails).not.toHaveBeenCalled()
})
})
describe('genealogical symbols setting', () => {
it('saves the selected symbol set', () => {
const element = new GrampsjsViewSettingsUser()
const updateSettings = vi.fn()
element.appState = {settings: {}, updateSettings}
element._handleSymbolsSelected({target: {value: SYMBOL_SET_TEXT}})
expect(updateSettings).toHaveBeenCalledWith({symbolSet: SYMBOL_SET_TEXT})
})
it('falls back to the default set for an invalid value', () => {
const element = new GrampsjsViewSettingsUser()
const updateSettings = vi.fn()
element.appState = {settings: {}, updateSettings}
element._handleSymbolsSelected({target: {value: 'nonsense'}})
expect(updateSettings).toHaveBeenCalledWith({symbolSet: SYMBOL_SET_DEFAULT})
})
})
+71
View File
@@ -0,0 +1,71 @@
import {describe, it, expect} from 'vitest'
import {
getSymbols,
SYMBOL_SET_DEFAULT,
SYMBOL_SET_TEXT,
SYMBOL_SET_OPTIONS,
} from '../../src/symbols.js'
describe('getSymbols', () => {
it('returns the default unicode symbols when settings is undefined', () => {
expect(getSymbols(undefined)).toEqual({
birthSymbol: '∗',
deathSymbol: '†',
marriageSymbol: '⚭',
divorceSymbol: '⚮',
})
})
it('returns the default unicode symbols when symbolSet is not set', () => {
expect(getSymbols({})).toEqual(getSymbols({symbolSet: SYMBOL_SET_DEFAULT}))
})
it('returns text abbreviations for the "text" set', () => {
expect(getSymbols({symbolSet: SYMBOL_SET_TEXT})).toEqual({
birthSymbol: 'b.',
deathSymbol: 'd.',
marriageSymbol: 'm.',
divorceSymbol: 'div.',
})
})
it('translates text abbreviations when a translator is provided', () => {
const translations = {
'birth abbreviation\u0004b.': 'geb.',
'death abbreviation\u0004d.': 'gest.',
'marriage abbreviation\u0004m.': 'verh.',
'Divorce abbreviation\u0004div.': 'gesch.',
}
expect(
getSymbols({symbolSet: SYMBOL_SET_TEXT}, value => translations[value])
).toEqual({
birthSymbol: 'geb.',
deathSymbol: 'gest.',
marriageSymbol: 'verh.',
divorceSymbol: 'gesch.',
})
})
it('falls back to visible abbreviations when contextual translations are missing', () => {
expect(getSymbols({symbolSet: SYMBOL_SET_TEXT}, () => undefined)).toEqual({
birthSymbol: 'b.',
deathSymbol: 'd.',
marriageSymbol: 'm.',
divorceSymbol: 'div.',
})
})
it('falls back to the default set for an unknown symbolSet', () => {
expect(getSymbols({symbolSet: 'nonsense'})).toEqual(
getSymbols({symbolSet: SYMBOL_SET_DEFAULT})
)
})
})
describe('SYMBOL_SET_OPTIONS', () => {
it('has one option per symbol set, with a matching getSymbols() result', () => {
SYMBOL_SET_OPTIONS.forEach(({value}) => {
expect(getSymbols({symbolSet: value})).toBeTruthy()
})
})
})
+12
View File
@@ -2,6 +2,7 @@ import {describe, it, expect} from 'vitest'
import {render, html} from 'lit'
import {
translate,
objectDetail,
personTitleFromProfile,
personDisplayName,
reportSelectItemLabel,
@@ -46,6 +47,17 @@ describe('translate', () => {
expect(translate(strings, 'Hello')).to.equal('Hola')
})
it('uses the configured text birth symbol in object detail', () => {
const result = objectDetail(
'person',
{profile: {birth: {date: '1990', place: 'Paris'}}},
{},
{symbolSet: 'text'}
)
expect(result).to.include('b. 1990')
expect(result).not.to.include('∗ 1990')
})
it('strips leading underscore', () => {
expect(translate(strings, '_Save')).to.equal('Guardar')
})