Resolve double redirects automatically

And test that it does so.
This commit is contained in:
Florine W. Dekker 2022-12-16 23:17:40 +01:00
parent 75cf6e4e77
commit c3fd28afaf
Signed by: FWDekker
GPG Key ID: D3DCFAA8A4560BE0
8 changed files with 190 additions and 26 deletions

View File

@ -1,7 +1,7 @@
{
"name": "fwdekker/death-notifier",
"description": "Get notified when a famous person dies.",
"version": "0.19.10", "_comment_version": "Also update version in `package.json`!",
"version": "0.19.11", "_comment_version": "Also update version in `package.json`!",
"type": "project",
"license": "MIT",
"homepage": "https://git.fwdekker.com/tools/death-notifier",

BIN
composer.lock generated

Binary file not shown.

BIN
package-lock.json generated

Binary file not shown.

View File

@ -1,6 +1,6 @@
{
"name": "death-notifier",
"version": "0.19.10", "_comment_version": "Also update version in `composer.json`!",
"version": "0.19.11", "_comment_version": "Also update version in `composer.json`!",
"description": "Get notified when a famous person dies.",
"author": "Florine W. Dekker",
"browser": "dist/bundle.js",

View File

@ -408,7 +408,7 @@
<small id="delete-account-email-hint" data-hint-for="delete-account-email"></small>
<input type="checkbox" role="switch" id="delete-account-confirm" />
<label for="delete-account-confirm">I want to permanently delete my account</label>
<label for="delete-account-confirm">I want to permanently delete my account.</label>
<br />
<small id="delete-account-confirm-hint" data-hint-for="delete-account-confirm"></small>
<br /><br />

View File

@ -19,22 +19,12 @@ class Redirects implements IteratorAggregate
/**
* @var array<string, string> the known redirects
*/
private readonly array $redirects;
private array $redirects = [];
/**
* Constructs a new `Redirects`.
*
* @param array<string, string> $redirects the redirects to start with
*/
public function __construct(array $redirects = [])
{
$this->redirects = $redirects;
}
/**
* Adds a single redirect, and returns a new `Redirects` containing the current redirects and the new redirects.
* Adds new redirects and resolves double redirects, and returns a new `Redirects` containing the old and new
* redirects.
*
* If there already exists a redirect `a -> b`, and the redirect `b -> c` is added, then `a -> b` is changed to
* `a -> c`.
@ -43,18 +33,24 @@ class Redirects implements IteratorAggregate
* `a -> c`.
*
* @param array<string, string> $redirects the redirects to add
* @return Redirects a new `Redirects` containing the current redirects and the new redirects
* @return Redirects a new `Redirects` containing the old and new redirects
*/
public function add_redirects(array $redirects): Redirects
public function add(array $redirects): Redirects
{
$new_redirects = $this->redirects;
$all_redirects = $this->redirects;
foreach ($redirects as $from => $to) {
$already_redirected = array_search($from, $new_redirects);
$new_redirects[$already_redirected === false ? $from : $already_redirected] = $to;
$to_redirects_to = $all_redirects[$to] ?? $to;
$all_redirects[$from] = $to_redirects_to;
$redirects_to_from = array_keys($all_redirects, $from);
foreach ($redirects_to_from as $redirect_to_from)
$all_redirects[$redirect_to_from] = $to_redirects_to;
}
return new Redirects($new_redirects);
$new_object = new Redirects();
$new_object->redirects = $all_redirects;
return $new_object;
}
/**
@ -65,7 +61,7 @@ class Redirects implements IteratorAggregate
*/
public function resolve(string $from): string
{
return !isset($this->redirects[$from]) ? $from : $this->redirects[$from];
return $this->redirects[$from] ?? $from;
}

View File

@ -133,14 +133,14 @@ class Wikipedia
array_map(fn($it) => strval($it), array_column($response["normalized"] ?? [], "from")),
array_map(fn($it) => strval($it), array_column($response["normalized"] ?? [], "to"))
);
$redirects = $redirects->add_redirects($new_normalizations);
$redirects = $redirects->add($new_normalizations);
$new_redirects =
array_combine(
array_map(fn($it) => strval($it), array_column($response["redirects"] ?? [], "from")),
array_map(fn($it) => strval($it), array_column($response["redirects"] ?? [], "to"))
);
$redirects = $redirects->add_redirects($new_redirects);
$redirects = $redirects->add($new_redirects);
}
return new QueryOutput($articles, $redirects, $missing);
@ -220,7 +220,7 @@ class Wikipedia
return new QueryOutput(
array_replace($output_base->results, $output_of_moves->results),
$output_base->redirects->add_redirects($moves),
$output_base->redirects->add($moves),
$not_moved
);
}

View File

@ -0,0 +1,168 @@
<?php
namespace com\fwdekker\deathnotifier\wikipedia;
use PHPUnit\Framework\TestCase;
/**
* Unit tests for {@see Redirects}.
*/
class RedirectsTest extends TestCase
{
public function test_resolve_returns_key_if_empty(): void
{
$redirects = new Redirects();
self::assertEquals("does-not-exist", $redirects->resolve("does-not-exist"));
}
public function test_resolve_returns_key_if_key_not_added(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["from" => "to"]);
self::assertEquals("does-not-exist", $redirects->resolve("does-not-exist"));
}
public function test_resolve_returns_target_if_key_added(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["from" => "to"]);
self::assertEquals("to", $redirects->resolve("from"));
}
public function test_resolve_returns_new_target_if_key_overridden(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["from" => "to"]);
$redirects = $redirects->add(["from" => "new"]);
self::assertEquals("new", $redirects->resolve("from"));
}
public function test_add_does_not_alter_old_object(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["from" => "to"]);
$new_redirects = $redirects->add(["from" => "new"]);
self::assertEquals("to", $redirects->resolve("from"));
self::assertEquals("new", $new_redirects->resolve("from"));
}
public function test_add_inserts_new_key(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["from" => "to"]);
self::assertEquals("to", $redirects->resolve("from"));
}
public function test_add_inserts_multiple_new_keys(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["a" => "one", "b" => "two", "c" => "three"]);
self::assertEquals("one", $redirects->resolve("a"));
self::assertEquals("two", $redirects->resolve("b"));
self::assertEquals("three", $redirects->resolve("c"));
}
public function test_merges_ab_and_bc_to_ac(): void
{
$redirects = new Redirects();
$redirects = $redirects->add([
"a" => "b",
"b" => "c",
]);
self::assertEquals("c", $redirects->resolve("a"));
self::assertEquals("c", $redirects->resolve("b"));
self::assertEquals("c", $redirects->resolve("c"));
}
public function test_merges_bc_and_ab_to_ac(): void
{
$redirects = new Redirects();
$redirects = $redirects->add([
"b" => "c",
"a" => "b",
]);
self::assertEquals("c", $redirects->resolve("a"));
self::assertEquals("c", $redirects->resolve("b"));
self::assertEquals("c", $redirects->resolve("c"));
}
public function test_merges_ab_cd_and_bc_to_ad(): void
{
$redirects = new Redirects();
$redirects = $redirects->add([
"a" => "b",
"c" => "d",
"b" => "c",
]);
self::assertEquals("d", $redirects->resolve("a"));
self::assertEquals("d", $redirects->resolve("b"));
self::assertEquals("d", $redirects->resolve("c"));
self::assertEquals("d", $redirects->resolve("d"));
}
public function test_has_non_reflexive_redirects_is_false_if_empty(): void
{
$redirects = new Redirects();
self::assertFalse($redirects->has_non_reflexive_redirects());
}
public function test_has_non_reflexive_redirects_is_false_if_all_are_reflexive(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["a" => "a", "b" => "b", "c" => "c"]);
self::assertFalse($redirects->has_non_reflexive_redirects());
}
public function test_has_non_reflexive_redirects_is_true_if_all_are_non_reflexive(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["a" => "one", "b" => "two", "c" => "three"]);
self::assertTrue($redirects->has_non_reflexive_redirects());
}
public function test_has_non_reflexive_redirects_is_true_if_some_are_non_reflexive(): void
{
$redirects = new Redirects();
$redirects = $redirects->add(["a" => "one", "self" => "self", "c" => "three"]);
self::assertTrue($redirects->has_non_reflexive_redirects());
}
public function test_iterator_iterates_over_all_redirects(): void
{
$inserted = ["a" => "one", "b" => "two", "c" => "three"];
$redirects = new Redirects();
$redirects = $redirects->add($inserted);
$extracted = [];
foreach ($redirects as $from => $to)
$extracted[$from] = $to;
self::assertTrue($extracted === $inserted);
}
}