1<?php 2 3/** 4 * webtrees: online genealogy 5 * Copyright (C) 2023 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 Fig\Http\Message\RequestMethodInterface; 23use Fig\Http\Message\StatusCodeInterface; 24use Fisharebest\Webtrees\Contracts\UserInterface; 25use Fisharebest\Webtrees\Module\IndividualListModule; 26use Fisharebest\Webtrees\Services\GedcomImportService; 27use Fisharebest\Webtrees\Services\TreeService; 28use Fisharebest\Webtrees\Services\UserService; 29 30use function array_map; 31use function preg_match_all; 32 33/** 34 * Test the individual lists. 35 * 36 * @coversNothing 37 */ 38class IndividualListTest extends TestCase 39{ 40 protected static bool $uses_database = true; 41 42 private IndividualListModule $module; 43 private Tree $tree; 44 private User $user; 45 46 public function setUp(): void 47 { 48 I18N::init('en-US'); 49 50 $this->module = new IndividualListModule(); 51 $user_service = new UserService(); 52 $tree_service = new TreeService(new GedcomImportService()); 53 $this->tree = $tree_service->create('name', 'title'); 54 $this->user = $user_service->create('user', 'User', 'user@example.com', 'secret'); 55 $this->user->setPreference(UserInterface::PREF_IS_ADMINISTRATOR, '1'); 56 $this->user->setPreference(UserInterface::PREF_AUTO_ACCEPT_EDITS, '1'); 57 Auth::login($this->user); 58 // The default "John Doe" individual will confuse the test results... 59 Registry::individualFactory()->make('X1', $this->tree)->deleteRecord(); 60 } 61 62 public function tearDown(): void 63 { 64 $tree_service = new TreeService(new GedcomImportService()); 65 $tree_service->delete($this->tree); 66 67 $user_service = new UserService(); 68 $user_service->delete($this->user); 69 } 70 71 /** 72 * @covers \Fisharebest\Webtrees\Module\IndividualListModule 73 */ 74 public function testCollationOfInitials(): void 75 { 76 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Âaa/"); 77 $this->tree->createIndividual("0 @@ INDI\n1 NAME /aaa/"); 78 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Ååå/"); 79 $this->tree->createIndividual("0 @@ INDI\n1 NAME /æææ/"); 80 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Caa/"); 81 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Css/"); 82 $this->tree->createIndividual("0 @@ INDI\n1 NAME /Dza/"); 83 84 I18N::init('en-US'); 85 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 86 $response = $this->module->handle($request); 87 $html = $response->getBody()->getContents(); 88 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 89 self::assertEquals(['A', 'C', 'D', 'Æ'], array_map(rawurldecode(...), $matches[1])); 90 91 I18N::init('sv'); 92 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 93 $response = $this->module->handle($request); 94 $html = $response->getBody()->getContents(); 95 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 96 self::assertEquals(['A', 'C', 'D', 'Å', 'Æ'], array_map(rawurldecode(...), $matches[1])); 97 98 I18N::init('hu'); 99 $request = self::createRequest(RequestMethodInterface::METHOD_GET, [], [], [], ['tree' => $this->tree]); 100 $response = $this->module->handle($request); 101 $html = $response->getBody()->getContents(); 102 preg_match_all('/%2Findividual-list&alpha=([^"&]+)/', $html, $matches); 103 self::assertEquals(['A', 'C', 'CS', 'DZ', 'Æ'], array_map(rawurldecode(...), $matches[1])); 104 } 105 106 /** 107 * @covers \Fisharebest\Webtrees\Module\IndividualListModule 108 */ 109 public function testRedirectToCanonicalSurname(): void 110 { 111 I18N::init('en-US'); 112 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MÜLLER'], [], [], ['tree' => $this->tree]); 113 $response = $this->module->handle($request); 114 self::assertSame(StatusCodeInterface::STATUS_MOVED_PERMANENTLY, $response->getStatusCode()); 115 self::assertStringContainsString('surname=MULLER', $response->getHeaderLine('Location')); 116 117 I18N::init('de'); 118 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MÜLLER'], [], [], ['tree' => $this->tree]); 119 $response = $this->module->handle($request); 120 self::assertSame(StatusCodeInterface::STATUS_MOVED_PERMANENTLY, $response->getStatusCode()); 121 self::assertStringContainsString('surname=MUELLER', $response->getHeaderLine('Location')); 122 } 123 124 /** 125 * @covers \Fisharebest\Webtrees\Module\IndividualListModule 126 */ 127 public function testCollationOfSurnames(): void 128 { 129 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Muller/"); 130 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Müller/"); 131 $i3 = $this->tree->createIndividual("0 @@ INDI\n1 NAME /Mueller/"); 132 133 I18N::init('en-US'); 134 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MULLER'], [], [], ['tree' => $this->tree]); 135 $response = $this->module->handle($request); 136 $html = $response->getBody()->getContents(); 137 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 138 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1], 'English, so U should match U and Ü'); 139 140 I18N::init('de'); 141 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MULLER'], [], [], ['tree' => $this->tree]); 142 $response = $this->module->handle($request); 143 $html = $response->getBody()->getContents(); 144 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 145 self::assertEqualsCanonicalizing([$i1->xref()], $matches[1], 'German, so U should only match U'); 146 147 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['surname' => 'MUELLER'], [], [], ['tree' => $this->tree]); 148 $response = $this->module->handle($request); 149 $html = $response->getBody()->getContents(); 150 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 151 self::assertEqualsCanonicalizing([$i2->xref(), $i3->xref()], $matches[1], 'German, so UE should also match Ü'); 152 } 153 154 /** 155 * @covers \Fisharebest\Webtrees\Module\IndividualListModule 156 */ 157 public function testUnknownVersusMissingSurname(): void 158 { 159 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John //"); 160 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John"); 161 162 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => '@'], [], [], ['tree' => $this->tree]); 163 $response = $this->module->handle($request); 164 $html = $response->getBody()->getContents(); 165 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 166 self::assertEqualsCanonicalizing([$i1->xref()], $matches[1]); 167 168 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => ','], [], [], ['tree' => $this->tree]); 169 $response = $this->module->handle($request); 170 $html = $response->getBody()->getContents(); 171 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 172 self::assertEqualsCanonicalizing([$i2->xref()], $matches[1]); 173 } 174 175 /** 176 * @covers \Fisharebest\Webtrees\Module\IndividualListModule 177 */ 178 public function testAllSurnamesExcludesUnknownAndMissing(): void 179 { 180 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John /Black/"); 181 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME Mary /White/"); 182 $this->tree->createIndividual("0 @@ INDI\n1 NAME Peter //"); 183 $this->tree->createIndividual("0 @@ INDI\n1 NAME Paul"); 184 185 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['show_all' => 'yes'], [], [], ['tree' => $this->tree]); 186 $response = $this->module->handle($request); 187 $html = $response->getBody()->getContents(); 188 preg_match_all('/individual-list&surname=([A-Z]+)/', $html, $matches); 189 self::assertEqualsCanonicalizing(['BLACK', 'WHITE'], $matches[1]); 190 191 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['show_all' => 'yes', 'show' => 'indi'], [], [], ['tree' => $this->tree]); 192 $response = $this->module->handle($request); 193 $html = $response->getBody()->getContents(); 194 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 195 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1]); 196 } 197 198 /** 199 * @covers \Fisharebest\Webtrees\Module\IndividualListModule 200 */ 201 public function testSurnameInitial(): void 202 { 203 $i1 = $this->tree->createIndividual("0 @@ INDI\n1 NAME John /Black/"); 204 $i2 = $this->tree->createIndividual("0 @@ INDI\n1 NAME Mary /Brown/"); 205 $this->tree->createIndividual("0 @@ INDI\n1 NAME Peter /White/"); 206 $this->tree->createIndividual("0 @@ INDI\n1 NAME Paul /Green/"); 207 208 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => 'B'], [], [], ['tree' => $this->tree]); 209 $response = $this->module->handle($request); 210 $html = $response->getBody()->getContents(); 211 preg_match_all('/individual-list&surname=([A-Z]+)/', $html, $matches); 212 self::assertEqualsCanonicalizing(['BLACK', 'BROWN'], $matches[1]); 213 214 $request = self::createRequest(RequestMethodInterface::METHOD_GET, ['alpha' => 'B', 'show' => 'indi'], [], [], ['tree' => $this->tree]); 215 $response = $this->module->handle($request); 216 $html = $response->getBody()->getContents(); 217 preg_match_all('/%2Fname%2Findividual%2F(X\d+)%2F/', $html, $matches); 218 self::assertEqualsCanonicalizing([$i1->xref(), $i2->xref()], $matches[1]); 219 } 220} 221