xref: /webtrees/tests/feature/RelationshipNamesTest.php (revision ac71572d8462e396ed5a307f05b29381e49f9e6e)
1<?php
2
3/**
4 * webtrees: online genealogy
5 * Copyright (C) 2022 webtrees development team
6 * This program is free software: you can redistribute it and/or modify
7 * it under the terms of the GNU General Public License as published by
8 * the Free Software Foundation, either version 3 of the License, or
9 * (at your option) any later version.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 * You should have received a copy of the GNU General Public License
15 * along with this program. If not, see <https://www.gnu.org/licenses/>.
16 */
17
18declare(strict_types=1);
19
20namespace Fisharebest\Webtrees;
21
22use Fisharebest\Webtrees\Factories\FamilyFactory;
23use Fisharebest\Webtrees\Factories\IndividualFactory;
24use Fisharebest\Webtrees\Module\LanguageEnglishAustralia;
25use Fisharebest\Webtrees\Module\LanguageEnglishGreatBritain;
26use Fisharebest\Webtrees\Module\LanguageEnglishUnitedStates;
27use Fisharebest\Webtrees\Module\LanguageFrench;
28use Fisharebest\Webtrees\Module\LanguageSlovakian;
29use Fisharebest\Webtrees\Module\ModuleLanguageInterface;
30use Fisharebest\Webtrees\Services\RelationshipService;
31
32use function array_reverse;
33
34/**
35 * Test the user functions
36 *
37 * @covers \Fisharebest\Webtrees\Relationship
38 * @covers \Fisharebest\Webtrees\Services\RelationshipService
39 * @covers \Fisharebest\Webtrees\Module\LanguageEnglishGreatBritain
40 * @covers \Fisharebest\Webtrees\Module\LanguageEnglishUnitedStates
41 * @covers \Fisharebest\Webtrees\Module\LanguageFrench
42 * @covers \Fisharebest\Webtrees\Module\ModuleLanguageTrait
43 */
44class RelationshipNamesTest extends TestCase
45{
46    protected static bool $uses_database = true;
47
48    /**
49     * @return void
50     */
51    public function testRelationshipNames(): void
52    {
53        //                                                   i22m===f10===i23f
54        //                                                           |
55        //                                                     +-----+-----+
56        //                                                     |           |
57        //                                       i20m===f9===i21f         i24m===f11m===i25f
58        //                                           |                            |
59        //                              i19f===f8===i18m                         i26f===f12===i27m
60        //                                      |                                        |
61        //                                  +---+---+-----+                          +---+---+
62        //                                  |       |     |                          |       |
63        //                    i16m===f7===i17f     i30m  i37f                       i28u    i29m===f15
64        //                            |                                                             |
65        //          +-----------------+                                                             |
66        //          |                 |                                                             |
67        //         i38f i12f===f4m===i11m  i13m===f5m===i14f                                       i34f===f16
68        //                      |                  |                                                       |
69        //                     i1m===f1m==========i2f===f2d===i6m=====f13m===i31m===f14d===i32f           i35m===f17
70        //                            |                  |                           |                            |
71        //                        +---+---+          +---+---+                 +-----+                            |
72        //                        |   |   |          |   |   |                 |     |                            |
73        //          i10f===f3e===i3m i4f i5u       i7ma i8f i9u===f6===i15u   i33f  i39mo                        i36f
74        //
75        // Individual suffixes - m(ale), f(emale), u(nknown), a(dopted), o(foster)
76        // Family suffixes - m(arried), d(ivorced), e(ngaged)
77        //
78        $tree = $this->createMock(Tree::class);
79
80        $individual_factory = $this->createStub(IndividualFactory::class);
81        $family_factory     = $this->createStub(FamilyFactory::class);
82
83        Registry::familyFactory($family_factory);
84        Registry::individualFactory($individual_factory);
85
86        $i1m  = new Individual('i1m', "0 @i1m@ INDI\n1 SEX M\n1 FAMS @f1m@\n1 FAMC @f4m@", null, $tree);
87        $i2f  = new Individual('i2f', "0 @i2f@ INDI\n1 SEX F\n1 FAMS @f1m@\n1 FAMS @f2d@\n2 FAMC @f5m@", null, $tree);
88        $i3m  = new Individual('i3m', "0 @i3m@ INDI\n1 SEX M\n1 FAMC @f1m@\n1 FAMS @f3e@\n1 BIRT\n2 DATE 2000", null, $tree);
89        $i4f  = new Individual('i4f', "0 @i4f@ INDI\n1 SEX F\n1 FAMC @f1m@\n1 BIRT\n2 DATE 2001", null, $tree);
90        $i5u  = new Individual('i5u', "0 @i5u@ INDI\n1 SEX U\n1 FAMC @f1m@\n1 BIRT\n2 DATE 2002", null, $tree);
91        $i6m  = new Individual('i6m', "0 @i6m@ INDI\n1 SEX M\n1 FAMS @f2d@", null, $tree);
92        $i7ma = new Individual('i7ma', "0 @i7ma@ INDI\n1 SEX M\n1 FAMC @f2d@\n2 PEDI adopted", null, $tree);
93        $i8f  = new Individual('i8f', "0 @i8f@ INDI\n1 SEX F\n1 FAMC @f2d@", null, $tree);
94        $i9u  = new Individual('i9u', "0 @i9u@ INDI\n1 SEX U\n1 FAMC @f2d@\n1 FAMS @f6@", null, $tree);
95        $i10f = new Individual('i10f', "0 @i10f@ INDI\n1 SEX F\n1 FAMS @f3e@", null, $tree);
96        $i11m = new Individual('i11m', "0 @i11f@ INDI\n1 SEX M\n1 FAMS @f4m@\n1 FAMC @f7@", null, $tree);
97        $i12f = new Individual('i12f', "0 @i12f@ INDI\n1 SEX F\n1 FAMS @f4m@", null, $tree);
98        $i13m = new Individual('i13m', "0 @i13f@ INDI\n1 SEX M\n1 FAMS @f5m@", null, $tree);
99        $i14f = new Individual('i14f', "0 @i14f@ INDI\n1 SEX F\n1 FAMS @f5m@", null, $tree);
100        $i15u = new Individual('i15u', "0 @i15u@ INDI\n1 SEX U\n1 FAMS @f6@", null, $tree);
101        $i16m = new Individual('i16m', "0 @i16m@ INDI\n1 SEX M\n1 FAMS @f7@", null, $tree);
102        $i17f = new Individual('i17f', "0 @i17f@ INDI\n1 SEX F\n1 FAMS @f7@\n1 FAMC @f8@", null, $tree);
103        $i18m = new Individual('i18m', "0 @i18m@ INDI\n1 SEX M\n1 FAMS @f8@\n1 FAMC @f9@", null, $tree);
104        $i19f = new Individual('i19f', "0 @i19f@ INDI\n1 SEX F\n1 FAMS @f8@", null, $tree);
105        $i20m = new Individual('i20m', "0 @i20m@ INDI\n1 SEX M\n1 FAMS @f9@", null, $tree);
106        $i21f = new Individual('i21f', "0 @i21f@ INDI\n1 SEX F\n1 FAMS @f9@\n1 FAMC @f10@", null, $tree);
107        $i22m = new Individual('i22m', "0 @i22m@ INDI\n1 SEX M\n1 FAMS @f10@", null, $tree);
108        $i23f = new Individual('i23f', "0 @i23f@ INDI\n1 SEX F\n1 FAMS @f10@", null, $tree);
109        $i24m = new Individual('i24m', "0 @i24m@ INDI\n1 SEX M\n1 FAMS @f11@\n1 FAMC @f10@", null, $tree);
110        $i25f = new Individual('i25f', "0 @i25f@ INDI\n1 SEX F\n1 FAMS @f11@", null, $tree);
111        $i26f = new Individual('i26f', "0 @i26f@ INDI\n1 SEX F\n1 FAMS @f12@\n1 FAMC @f11@", null, $tree);
112        $i27m = new Individual('i27m', "0 @i27m@ INDI\n1 SEX M\n1 FAMS @f12@", null, $tree);
113        $i28u = new Individual('i28u', "0 @i28u@ INDI\n1 SEX U\n1 FAMC @f12@", null, $tree);
114        $i29m = new Individual('i29m', "0 @i29m@ INDI\n1 SEX M\n1 FAMC @f12@", null, $tree);
115        $i30m = new Individual('i30m', "0 @i30m@ INDI\n1 SEX M\n1 FAMC @f8@", null, $tree);
116        $i31m = new Individual('i31m', "0 @i31m@ INDI\n1 SEX M\n1 FAMS @f13m@\n1 FAMS @f14d@", null, $tree);
117        $i32f = new Individual('i32f', "0 @i32f@ INDI\n1 SEX F\n1 FAMS @f14d@", null, $tree);
118        $i33f = new Individual('i33f', "0 @i33f@ INDI\n1 SEX F\n1 FAMC @f14d@", null, $tree);
119        $i34f = new Individual('i34f', "0 @i34f@ INDI\n1 SEX F\n1 FAMC @f15@", null, $tree);
120        $i35m = new Individual('i35m', "0 @i35m@ INDI\n1 SEX M\n1 FAMS @f17@\n1 FAMC @f16@", null, $tree);
121        $i36f = new Individual('i36f', "0 @i36f@ INDI\n1 SEX F\n1 FAMC @f17@", null, $tree);
122        $i37f = new Individual('i37f', "0 @i37f@ INDI\n1 SEX F\n1 FAMC @f8@", null, $tree);
123        $i38f = new Individual('i38f', "0 @i38f@ INDI\n1 SEX F\n1 FAMC @f7@", null, $tree);
124        $i39mo = new Individual('i39mo', "0 @i39o@ INDI\n1 SEX M\n1 FAMC @f14d@\n2 PEDI foster", null, $tree);
125
126        $individual_factory->method('make')->willReturnMap([
127            'i1m'   => $i1m,
128            'i2f'   => $i2f,
129            'i3m'   => $i3m,
130            'i4f'   => $i4f,
131            'i5u'   => $i5u,
132            'i6m'   => $i6m,
133            'i7ma'  => $i7ma,
134            'i8f'   => $i8f,
135            'i9u'   => $i9u,
136            'i10f'  => $i10f,
137            'i11m'  => $i11m,
138            'i12f'  => $i12f,
139            'i13m'  => $i13m,
140            'i14f'  => $i14f,
141            'i15u'  => $i15u,
142            'i16m'  => $i16m,
143            'i17f'  => $i17f,
144            'i18m'  => $i18m,
145            'i19f'  => $i19f,
146            'i20m'  => $i20m,
147            'i21f'  => $i21f,
148            'i22m'  => $i22m,
149            'i23f'  => $i23f,
150            'i24m'  => $i24m,
151            'i25f'  => $i25f,
152            'i26f'  => $i26f,
153            'i27m'  => $i27m,
154            'i28u'  => $i28u,
155            'i29m'  => $i29m,
156            'i30m'  => $i30m,
157            'i31m'  => $i31m,
158            'i32f'  => $i32f,
159            'i33f'  => $i33f,
160            'i34f'  => $i34f,
161            'i35m'  => $i35m,
162            'i36f'  => $i36f,
163            'i37f'  => $i37f,
164            'i38f'  => $i38f,
165            'i39mo' => $i39mo
166        ]);
167
168        $f1m  = new Family('f1m', "0 @f1m@ FAM\n1 MARR Y\n1 HUSB @i1m@\n1 WIFE @i2f@\n1 CHIL @i3m@\n1 CHIL @i4f@\n1 CHIL @i5u@", null, $tree);
169        $f2d  = new Family('f2d', "0 @f2d@ FAM\n1 DIV Y\n1 HUSB @i6m@\n1 WIFE @i2f@\n1 CHIL @i7ma@\n1 CHIL @i8f@\n1 CHIL @i9u@", null, $tree);
170        $f3e  = new Family('f3e', "0 @f3e@ FAM\n1 ENGA Y\n1 HUSB @i3m@\n1 WIFE @i10f@", null, $tree);
171        $f4m  = new Family('f4m', "0 @f4m@ FAM\n1 MARR Y\n1 HUSB @i11m@\n1 WIFE @i12f@\n1 CHIL @i1m@", null, $tree);
172        $f5m  = new Family('f5m', "0 @f5m@ FAM\n1 MARR Y\n1 HUSB @i13m@\n1 WIFE @i14f@\n1 CHIL @i2f@", null, $tree);
173        $f6   = new Family('f6', "0 @f6@ FAM\n1 HUSB @i9u@\n1 WIFE @i15u@", null, $tree);
174        $f7   = new Family('f7', "0 @f7@ FAM\n1 HUSB @i16m@\n1 WIFE @i17f@\n1 CHIL @i11m@\n1 CHIL @i38f@", null, $tree);
175        $f8   = new Family('f8', "0 @f8@ FAM\n1 HUSB @i18m@\n1 WIFE @i19f@\n1 CHIL @i17f@\n1 CHIL @i30m@\n1 CHIL @i37f@", null, $tree);
176        $f9   = new Family('f9', "0 @f9@ FAM\n1 HUSB @i20m@\n1 WIFE @i21f@\n1 CHIL @i18m@", null, $tree);
177        $f10  = new Family('f10', "0 @f10@ FAM\n1 HUSB @i22m@\n1 WIFE @i23f@\n1 CHIL @i21f@\n1 CHIL @i24m@", null, $tree);
178        $f11m = new Family('f11m', "0 @f11m@ FAM\n1 MARR Y\n1 HUSB @i24m@\n1 WIFE @i25f@\n1 CHIL @i26f@", null, $tree);
179        $f12  = new Family('f12', "0 @f12@ FAM\n1 HUSB @i27m@\n1 WIFE @i26f@\n1 CHIL @i28u@\n1 CHIL @i29m@", null, $tree);
180        $f13m = new Family('f13m', "0 @f13m@ FAM\n1 MARR Y\n1 HUSB @i6m@\n1 WIFE @i31m@", null, $tree);
181        $f14d = new Family('f14d', "0 @f14d@ FAM\n1 DIV Y\n1 HUSB @i31m@\n1 WIFE @i32f@\n1 CHIL @i33f@\n1 CHIL @i39mo@", null, $tree);
182        $f15  = new Family('f15', "0 @f15@ FAM\n1 HUSB @i29m@\n1 CHIL @i34f@", null, $tree);
183        $f16  = new Family('f16', "0 @f16@ FAM\n1 WIFE @i34f@\n1 CHIL @i35m@", null, $tree);
184        $f17  = new Family('f17', "0 @f17@ FAM\n1 HUSB @i35m@\n1 CHIL @i36f@", null, $tree);
185
186        $family_factory->method('make')->willReturnMap([
187            'f1m'  => $f1m,
188            'f2d'  => $f2d,
189            'f3e'  => $f3e,
190            'f4m'  => $f4m,
191            'f5m'  => $f5m,
192            'f6'   => $f6,
193            'f7'   => $f7,
194            'f8'   => $f8,
195            'f9'   => $f9,
196            'f10'  => $f10,
197            'f11m' => $f11m,
198            'f12'  => $f12,
199            'f13m' => $f13m,
200            'f14d' => $f14d,
201            'f15'  => $f15,
202            'f16'  => $f16,
203            'f17'  => $f17
204        ]);
205
206        ///////////////////////////////////////////////////////////////////////
207        // ENGLISH
208        ///////////////////////////////////////////////////////////////////////
209
210        $en_au = new LanguageEnglishAustralia();
211        $en_gb = new LanguageEnglishGreatBritain();
212        $en_us = new LanguageEnglishUnitedStates();
213
214        foreach ([$en_us, $en_gb, $en_au] as $en) {
215            self::assertRelationships('wife', 'husband', [$i1m, $f1m, $i2f], $en);
216            self::assertRelationships('partner', 'partner', [$i9u, $f6, $i15u], $en);
217            self::assertRelationships('ex-husband', 'ex-wife', [$i2f, $f2d, $i6m], $en);
218            self::assertRelationships('fiancé', 'fiancée', [$i10f, $f3e, $i3m], $en);
219            self::assertRelationships('son', 'father', [$i1m, $f1m, $i3m], $en);
220            self::assertRelationships('daughter', 'mother', [$i2f, $f1m, $i4f], $en);
221            self::assertRelationships('child', 'father', [$i1m, $f1m, $i5u], $en);
222            self::assertRelationships('elder brother', 'younger sister', [$i4f, $f1m, $i3m], $en);
223            self::assertRelationships('younger sibling', 'elder brother', [$i3m, $f1m, $i5u], $en);
224            self::assertRelationships('brother', 'sister', [$i8f, $f2d, $i7ma], $en);
225            self::assertRelationships('sibling', 'brother', [$i7ma, $f2d, $i9u], $en);
226            self::assertRelationships('adoptive-mother', 'adopted-son', [$i7ma, $f2d, $i2f], $en);
227            self::assertRelationships('stepfather', 'stepchild', [$i9u, $f2d, $i2f, $f1m, $i1m], $en);
228            self::assertRelationships('stepdaughter', 'stepmother', [$i2f, $f1m, $i2f, $f2d, $i8f], $en);
229            self::assertRelationships('stepsister', 'stepsibling', [$i9u, $f2d, $i6m, $f13m, $i31m, $f14d, $i33f], $en);
230            self::assertRelationships('half-brother', 'half-sister', [$i8f, $f2d, $i2f, $f1m, $i3m], $en);
231            self::assertRelationships('mother-in-law', 'daughter-in-law', [$i2f, $f1m, $i1m, $f4m, $i12f], $en);
232            self::assertRelationships('paternal-grandfather', 'grandson', [$i3m, $f1m, $i1m, $f4m, $i11m], $en);
233            self::assertRelationships('paternal-grandmother', 'granddaughter', [$i4f, $f1m, $i1m, $f4m, $i12f], $en);
234            self::assertRelationships('maternal-grandfather', 'grandson', [$i3m, $f1m, $i2f, $f5m, $i13m], $en);
235            self::assertRelationships('maternal-grandmother', 'grandchild', [$i5u, $f1m, $i2f, $f5m, $i14f], $en);
236            self::assertRelationships('paternal great-grandfather', 'great-grandson', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i16m], $en);
237            self::assertRelationships('paternal great-grandmother', 'great-granddaughter', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f], $en);
238            self::assertRelationships('paternal great-great-grandfather', 'great-great-grandson', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m], $en);
239            self::assertRelationships('paternal great-great-grandmother', 'great-great-granddaughter', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i19f], $en);
240            self::assertRelationships('paternal great-great-great-grandfather', 'great-great-great-grandson', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i20m], $en);
241            self::assertRelationships('paternal great-great-great-grandmother', 'great-great-great-granddaughter', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f], $en);
242            self::assertRelationships('paternal great ×4 grandfather', 'great ×4 grandson', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i22m], $en);
243            self::assertRelationships('paternal great ×4 grandmother', 'great ×4 granddaughter', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i23f], $en);
244            self::assertRelationships('aunt', 'niece', [$i38f, $f7, $i17f, $f8, $i37f], $en);
245            self::assertRelationships('great-uncle', 'great-nephew', [$i30m, $f8, $i18m, $f9, $i21f, $f10, $i24m], $en);
246            self::assertRelationships('great-great-uncle', 'great-great-nephew', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m], $en);
247            self::assertRelationships('nephew', 'uncle', [$i24m, $f10, $i21f, $f9, $i18m], $en);
248            self::assertRelationships('great-niece', 'great-uncle', [$i24m, $f10, $i21f, $f9, $i18m, $f8, $i17f], $en);
249            self::assertRelationships('great-great-nephew', 'great-great-uncle', [$i24m, $f10, $i21f, $f9, $i18m, $f8, $i17f, $f7, $i11m], $en);
250            self::assertRelationships('first cousin', 'first cousin', [$i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $en);
251            self::assertRelationships('second cousin', 'second cousin', [$i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m], $en);
252            self::assertRelationships('first cousin once removed ascending', 'first cousin once removed descending', [$i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $en);
253            self::assertRelationships('third cousin', 'third cousin', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m, $f15, $i34f], $en);
254            self::assertRelationships('second cousin once removed ascending', 'second cousin once removed descending', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m], $en);
255            self::assertRelationships('first cousin twice removed ascending', 'first cousin twice removed descending', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $en);
256            self::assertRelationships('fourth cousin', 'fourth cousin', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m, $f15, $i34f, $f16, $i35m], $en);
257            self::assertRelationships('third cousin once removed ascending', 'third cousin once removed descending', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m, $f15, $i34f], $en);
258            self::assertRelationships('second cousin twice removed ascending', 'second cousin twice removed descending', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m], $en);
259            // Compound relationships
260            self::assertRelationships('wife’s ex-husband', 'ex-wife’s husband', [$i1m, $f1m, $i2f, $f2d, $i6m], $en);
261        }
262
263        // This relationship has a different name in different variants of English.
264        self::assertRelationships('first cousin thrice removed ascending', 'first cousin thrice removed descending', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $en_au);
265        self::assertRelationships('first cousin thrice removed ascending', 'first cousin thrice removed descending', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $en_gb);
266        self::assertRelationships('first cousin three times removed ascending', 'first cousin three times removed descending', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $en_us);
267
268        ///////////////////////////////////////////////////////////////////////
269        // FRENCH
270        ///////////////////////////////////////////////////////////////////////
271
272        $fr_fr = new LanguageFrench();
273        $fr_ca = new LanguageFrench();
274
275        foreach ([$fr_fr, $fr_ca] as $fr) {
276            self::assertRelationships('épouse', 'époux', [$i1m, $f1m, $i2f], $fr);
277            self::assertRelationships('conjoint', 'conjoint', [$i9u, $f6, $i15u], $fr);
278            self::assertRelationships('ex-époux', 'ex-épouse', [$i2f, $f2d, $i6m], $fr);
279            self::assertRelationships('fiancé', 'fiancée', [$i10f, $f3e, $i3m], $fr);
280            self::assertRelationships('fils', 'père', [$i1m, $f1m, $i3m], $fr);
281            self::assertRelationships('fille', 'mère', [$i2f, $f1m, $i4f], $fr);
282            self::assertRelationships('enfant', 'père', [$i1m, $f1m, $i5u], $fr);
283            self::assertRelationships('grand frère', 'petite sœur', [$i4f, $f1m, $i3m], $fr);
284            self::assertRelationships('petit frère/sœur', 'grand frère', [$i3m, $f1m, $i5u], $fr);
285            self::assertRelationships('frère', 'sœur', [$i38f, $f7, $i11m], $fr);
286            self::assertRelationships('frère/sœur', 'sœur', [$i8f, $f2d, $i9u], $fr);
287            self::assertRelationships('mère adoptive', 'fils adoptif', [$i7ma, $f2d, $i2f], $fr);
288            self::assertRelationships('père adoptif', 'fils adoptif', [$i7ma, $f2d, $i6m], $fr);
289            self::assertRelationships('sœur adoptive', 'frère adoptif', [$i7ma, $f2d, $i8f], $fr);
290            self::assertRelationships('mère d’accueil', 'fils accueilli', [$i39mo, $f14d, $i32f], $fr);
291            self::assertRelationships('père d’accueil', 'fils accueilli', [$i39mo, $f14d, $i31m], $fr);
292            self::assertRelationships('sœur d’accueil', 'frère accueilli', [$i39mo, $f14d, $i33f], $fr);
293            self::assertRelationships('beau-père', 'belle-fille', [$i8f, $f2d, $i2f, $f1m, $i1m], $fr);
294            self::assertRelationships('demi-frère', 'demi-frère/sœur', [$i9u, $f2d, $i2f, $f1m, $i3m], $fr);
295            self::assertRelationship('quasi-sœur', [$i8f, $f2d, $i6m, $f13m, $i31m, $f14d, $i33f], $fr);
296            self::assertRelationships('beau-père', 'gendre', [$i1m, $f1m, $i2f, $f5m, $i13m], $fr);
297            self::assertRelationships('belle-mère', 'bru', [$i2f, $f1m, $i1m, $f4m, $i12f], $fr);
298            self::assertRelationships('grand-père paternel', 'petit-fils', [$i3m, $f1m, $i1m, $f4m, $i11m], $fr);
299            self::assertRelationships('grand-mère paternelle', 'petite-fille', [$i4f, $f1m, $i1m, $f4m, $i12f], $fr);
300            self::assertRelationships('grand-père maternel', 'petit-fils', [$i3m, $f1m, $i2f, $f5m, $i13m], $fr);
301            self::assertRelationships('grand-mère maternelle', 'petite-fille', [$i4f, $f1m, $i2f, $f5m, $i14f], $fr);
302            self::assertRelationships('arrière-grand-père paternel', 'arrière-petit-fils', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i16m], $fr);
303            self::assertRelationships('arrière-grand-mère paternelle', 'arrière-petite-fille', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f], $fr);
304            self::assertRelationships('trisaïeul paternel', 'petit-fils au 3<sup>e</sup> degré', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m], $fr);
305            self::assertRelationships('trisaïeule paternelle', 'petite-fille au 3<sup>e</sup> degré', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i19f], $fr);
306            self::assertRelationships('grand-père paternel au 4<sup>e</sup> degré', 'petit-fils au 4<sup>e</sup> degré', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i20m], $fr);
307            self::assertRelationships('grand-mère paternelle au 4<sup>e</sup> degré', 'petite-fille au 4<sup>e</sup> degré', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f], $fr);
308            self::assertRelationships('oncle', 'neveu', [$i18m, $f9, $i21f, $f10, $i24m], $fr);
309            self::assertRelationships('grand-oncle', 'petite-nièce', [$i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m], $fr);
310            self::assertRelationships('arrière-grand-oncle', 'arrière-petit-neveu', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m], $fr);
311            self::assertRelationships('grand-oncle au 3<sup>e</sup> degré', 'petit-neveu au 3<sup>e</sup> degré', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m], $fr);
312            self::assertRelationships('grand-oncle au 4<sup>e</sup> degré', 'petite-nièce au 4<sup>e</sup> degré', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m], $fr);
313            self::assertRelationships('cousine germaine', 'cousin germain', [$i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $fr);
314            self::assertRelationships('cousin issu de germain', 'cousin issu de germain', [$i30m, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m], $fr);
315            self::assertRelationships('cousine au 3<sup>e</sup> degré', 'cousin au 3<sup>e</sup> degré', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m, $f15, $i34f], $fr);
316            self::assertRelationships('grand-cousine', 'petit-cousin', [$i30m, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $fr);
317            self::assertRelationships('petit-cousin', 'grand-cousine', [$i26f, $f11m, $i24m, $f10, $i21f, $f9, $i18m, $f8, $i30m], $fr);
318            self::assertRelationships('cousin du 2<sup>e</sup> au 3<sup>e</sup> degré', 'cousin du 3<sup>e</sup> au 2<sup>e</sup> degré', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m], $fr);
319            self::assertRelationships('cousine du 3<sup>e</sup> au 2<sup>e</sup> degré', 'cousine du 2<sup>e</sup> au 3<sup>e</sup> degré', [$i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m, $f15, $i34f], $fr);
320            // Compound relationships
321            self::assertRelationships('ex-époux de l’épouse', 'époux de l’ex-épouse', [$i1m, $f1m, $i2f, $f2d, $i6m], $fr);
322            self::assertRelationship('fiancée du petit-neveu au 4<sup>e</sup> degré', [$i24m, $f10, $i21f, $f9, $i18m, $f8, $i17f, $f7, $i11m, $f4m, $i1m, $f1m, $i3m, $f3e, $i10f], $fr);
323            self::assertRelationship('épouse de l’arrière-petit-neveu', [$i24m, $f10, $i21f, $f9, $i18m, $f8, $i17f, $f7, $i11m, $f4m, $i12f], $fr);
324            self::assertRelationship('conjoint de la petite-nièce', [$i24m, $f10, $i21f, $f9, $i18m, $f8, $i17f, $f7, $i16m], $fr);
325            self::assertRelationship('conjointe du neveu', [$i24m, $f10, $i21f, $f9, $i18m, $f8, $i19f], $fr);
326            self::assertRelationships('cousine germaine du conjoint', 'conjointe du cousin germain', [$i19f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $fr);
327        }
328
329        ///////////////////////////////////////////////////////////////////////
330        // SLOVAK
331        ///////////////////////////////////////////////////////////////////////
332
333        $sk = new LanguageSlovakian();
334
335        self::assertRelationships('manželka', 'manžel', [$i1m, $f1m, $i2f], $sk);
336        self::assertRelationships('partner', 'partner', [$i9u, $f6, $i15u], $sk);
337        self::assertRelationships('exmanžel', 'exmanželka', [$i2f, $f2d, $i6m], $sk);
338        self::assertRelationships('snúbenica', 'snúbenec', [$i10f, $f3e, $i3m], $sk);
339        self::assertRelationships('syn', 'otec', [$i1m, $f1m, $i3m], $sk);
340        self::assertRelationships('dcéra', 'matka', [$i2f, $f1m, $i4f], $sk);
341        self::assertRelationships('dieťa', 'otec', [$i1m, $f1m, $i5u], $sk);
342        self::assertRelationships('brat', 'sestra', [$i4f, $f1m, $i3m], $sk);
343        self::assertRelationships('súrodenec', 'brat', [$i3m, $f1m, $i5u], $sk);
344        self::assertRelationships('brat', 'sestra', [$i8f, $f2d, $i7ma], $sk);
345        self::assertRelationships('súrodenec', 'brat', [$i7ma, $f2d, $i9u], $sk);
346        self::assertRelationships('matka', 'syn', [$i7ma, $f2d, $i2f], $sk);
347        self::assertRelationships('manžel matky', 'dieťa manželky', [$i9u, $f2d, $i2f, $f1m, $i1m], $sk);
348        self::assertRelationships('dcéra manželky', 'manželka matky', [$i2f, $f1m, $i2f, $f2d, $i8f], $sk);
349        self::assertRelationships('dcéra manžela otca', 'dieťa manžela otca', [$i9u, $f2d, $i6m, $f13m, $i31m, $f14d, $i33f], $sk);
350        self::assertRelationships('nevlastný brat', 'nevlastná sestra', [$i8f, $f2d, $i2f, $f1m, $i3m], $sk);
351        self::assertRelationships('svokra', 'nevesta', [$i2f, $f1m, $i1m, $f4m, $i12f], $sk);
352        self::assertRelationships('starý otec', 'vnuk', [$i3m, $f1m, $i1m, $f4m, $i11m], $sk);
353        self::assertRelationships('stará matka', 'vnučka', [$i4f, $f1m, $i1m, $f4m, $i12f], $sk);
354        self::assertRelationships('starý otec', 'vnuk', [$i3m, $f1m, $i2f, $f5m, $i13m], $sk);
355        self::assertRelationships('stará matka', 'vnúča', [$i5u, $f1m, $i2f, $f5m, $i14f], $sk);
356        self::assertRelationships('prastarý otec', 'pravnuk', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i16m], $sk);
357        self::assertRelationships('prastarý otec', 'pravnučka', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f], $sk);
358        self::assertRelationships('pra-pra-pra-prastarý otec', 'pravnuk dcéry', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m], $sk);
359        self::assertRelationships('pra-pra-pra-prastará matka', 'pravnučka dcéry', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i19f], $sk);
360        self::assertRelationships('pra ×4 prastarý otec', 'pravnuk vnučky', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i20m], $sk);
361        self::assertRelationships('pra ×4 prastará matka', 'pravnučka vnučky', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f], $sk);
362        self::assertRelationships('pra ×5 prastarý otec', 'pravnuk pravnučky', [$i3m, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i22m], $sk);
363        self::assertRelationships('pra ×5 prastará matka', 'pravnučka pravnučky', [$i4f, $f1m, $i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i23f], $sk);
364        self::assertRelationships('teta', 'neter', [$i38f, $f7, $i17f, $f8, $i37f], $sk);
365        self::assertRelationships('prastrýko', 'prasynovec', [$i30m, $f8, $i18m, $f9, $i21f, $f10, $i24m], $sk);
366        self::assertRelationships('pra-prastrýko', 'pravnuk sestry', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m], $sk);
367        self::assertRelationships('synovec', 'ujo', [$i24m, $f10, $i21f, $f9, $i18m], $sk);
368        self::assertRelationships('praneter', 'prastrýko', [$i24m, $f10, $i21f, $f9, $i18m, $f8, $i17f], $sk);
369        self::assertRelationships('pravnuk sestry', 'pra-prastrýko', [$i24m, $f10, $i21f, $f9, $i18m, $f8, $i17f, $f7, $i11m], $sk);
370        self::assertRelationships('sesternica', 'bratranec', [$i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $sk);
371        self::assertRelationships('druhostupňový bratranec', 'druhostupňová sesternica', [$i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m], $sk);
372        self::assertRelationships('sesternica otca', 'praneter otca', [$i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $sk);
373        self::assertRelationships('sesternica z 3. kolena', 'bratranec z 3. kolena', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m, $f15, $i34f], $sk);
374        self::assertRelationships('druhostupňový bratranec matky', 'syn druhostupňovej sesternice', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m], $sk);
375        self::assertRelationships('pra-dcéra prastrýka', 'pravnuk tety', [$i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $sk);
376        self::assertRelationships('bratranec zo 4. kolena', 'bratranec zo 4. kolena', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m, $f15, $i34f, $f16, $i35m], $sk);
377        self::assertRelationships('sesternica z 3. kolena otca', 'syn bratranca z 3. kolena', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m, $f15, $i34f], $sk);
378        self::assertRelationships('druhostupňový bratranec starej matky', 'vnuk druhostupňovej sesternice', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f, $f12, $i29m], $sk);
379        self::assertRelationships('pra-pra-dcéra prastrýka', 'pravnuk bratranca', [$i1m, $f4m, $i11m, $f7, $i17f, $f8, $i18m, $f9, $i21f, $f10, $i24m, $f11m, $i26f], $sk);        // Compound relationships
380        self::assertRelationships('exmanžel manželky', 'manžel exmanželky', [$i1m, $f1m, $i2f, $f2d, $i6m], $sk);
381    }
382
383    /**
384     * @param string                   $expected
385     * @param array<Individual|Family> $nodes
386     * @param ModuleLanguageInterface  $language
387     */
388    private static function assertRelationship(string $expected, array $nodes, ModuleLanguageInterface $language): void
389    {
390        $service = new RelationshipService();
391        $actual  = $service->nameFromPath($nodes, $language);
392        $path    = implode('-', array_map(static fn (GedcomRecord $record): string => $record->xref(), $nodes));
393        $english = $service->nameFromPath($nodes, new LanguageEnglishUnitedStates());
394        $message = 'Language: ' . $language->title() . PHP_EOL . 'Path: ' . $path . ' (' . $english . ')';
395
396        self::assertSame($expected, $actual, $message);
397    }
398
399    /**
400     * Test a relationship name in both directions
401     *
402     * @param string                   $fwd
403     * @param string                   $rev
404     * @param array<Individual|Family> $nodes
405     * @param ModuleLanguageInterface  $language
406     */
407    private static function assertRelationships(string $fwd, string $rev, array $nodes, ModuleLanguageInterface $language): void
408    {
409        self::assertRelationship($fwd, $nodes, $language);
410        self::assertRelationship($rev, array_reverse($nodes), $language);
411    }
412}
413