Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/data-refresh-validation.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
cache: "npm"
Expand Down
8 changes: 4 additions & 4 deletions .github/workflows/data-refresh.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -21,10 +21,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
cache: "npm"
Expand Down Expand Up @@ -69,10 +69,10 @@ jobs:

steps:
- name: Checkout repository
uses: actions/checkout@v4
uses: actions/checkout@v6

- name: Setup Node.js
uses: actions/setup-node@v4
uses: actions/setup-node@v6
with:
node-version: "22"
cache: "npm"
Expand Down
10 changes: 5 additions & 5 deletions src/urls.ts
Original file line number Diff line number Diff line change
Expand Up @@ -122,7 +122,7 @@ export const getDownloadUrls = ({
};

export const getUserProfileUrl = ({ username }: { username: string }) =>
new URL(`/users/${encodeURI(username)}/profile`, getArchiveBaseUrl()).href;
new URL(`/users/${encodeURIComponent(username)}/profile`, getArchiveBaseUrl()).href;

const USER_WORKS_SORT_COLUMNS: Record<UserWorksFilters["sortColumn"], string> = {
authors: "authors_to_sort_on",
Expand All @@ -145,11 +145,11 @@ export const getUserWorksUrl = ({
}: Partial<UserWorksFilters> & { username: string; pseud?: string }) => {
// Pseud names are only unique within an account, so AO3 nests them under
// each user.
const userPath = `/users/${encodeURI(username)}`;
const userPath = `/users/${encodeURIComponent(username)}`;
const url = new URL(
pseud === undefined
? `${userPath}/works`
: `${userPath}/pseuds/${encodeURI(pseud)}/works`,
: `${userPath}/pseuds/${encodeURIComponent(pseud)}/works`,
getArchiveBaseUrl(),
);

Expand Down Expand Up @@ -202,11 +202,11 @@ const REPLACE_TOKENS_REGEX = new RegExp(

export const getTagUrl = (tagName: string) =>
new URL(
`tags/${encodeURI(tagName).replaceAll(
`tags/${encodeURIComponent(tagName.replaceAll(
REPLACE_TOKENS_REGEX,
(char: string) =>
isReplaceableToken(char) ? TOKEN_REPLACEMENTS_MAP[char] : char,
)}/`,
))}/`,
getArchiveBaseUrl(),
).href;

Expand Down
34 changes: 32 additions & 2 deletions tests/helpers/invariants.ts
Original file line number Diff line number Diff line change
@@ -1,10 +1,38 @@
import { expect } from "vitest";
import { prettify } from "htmlfy";

export function expectHtml(html: string | null) {
return expect(html === null ? null : prettify(html));
}

interface DriftingCountMatchers<R = unknown> {
driftingCount(bounds: { atLeast: number; atMost: number }): R;
}

interface HtmlMatchers<R = unknown> {
toEqualHtml(expected: string | null): R;
}

expect.extend({
toEqualHtml(received: unknown, expected: string | null) {
if (
(received !== null && typeof received !== "string") ||
(expected !== null && typeof expected !== "string")
) {
throw new TypeError("toEqualHtml requires HTML strings or null");
}

const actualHtml = received === null ? null : prettify(received);
const expectedHtml = expected === null ? null : prettify(expected);

return {
pass: actualHtml === expectedHtml,
message: () =>
`expected HTML ${this.isNot ? "not " : ""}to match after formatting`,
actual: actualHtml,
expected: expectedHtml,
};
},
driftingCount(
received: unknown,
bounds: Parameters<DriftingCountMatchers["driftingCount"]>[0],
Expand All @@ -28,6 +56,8 @@ expect.extend({
});

declare module "vitest" {
interface Assertion<T = any> extends DriftingCountMatchers<T> {}
interface AsymmetricMatchersContaining extends DriftingCountMatchers {}
interface Assertion<T = any>
extends DriftingCountMatchers<T>, HtmlMatchers<T> {}
interface AsymmetricMatchersContaining
extends DriftingCountMatchers, HtmlMatchers {}
}
37 changes: 19 additions & 18 deletions tests/mocks/data/ao3/series/1728802/index.html
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@
<script src="/javascripts/livevalidation_standalone.js"></script>

<meta name="csrf-param" content="authenticity_token" />
<meta name="csrf-token" content="tttyIq8TvP-0NPE8DoiyWTEDtiWhmvNtC6CnF-wP-f5qvh0QNEOzXsnjf3DWKzufTQ81omOYsTQm6F7Gvq5Klg" />
<meta name="csrf-token" content="qITexmpDK7JhtJAoGGQh30FkNNkYTHgiHx162g2vdIMYWpsuOWtyhZueHFK4KeOXFoe06YvRQzMw91E4VBsrVA" />


</head>
Expand All @@ -57,7 +57,7 @@ <h1 class="heading">
<a id="login-dropdown" href="/users/login?return_to=%2Fseries%2F1728802%3Fview_adult%3Dtrue">Log In</a>
</p>
<div id="small_login" class="simple login">
<form class="new_user" id="new_user_session_small" action="/users/login?return_to=%2Fseries%2F1728802%3Fview_adult%3Dtrue" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="cEda9_veO6rICBkLAZxdB4zAJmMP_hJNcwmMjR16bvt1fN1jiWmWpAHcxncVaudqNQ9ERL88XCj8kw6ohCARcg" autocomplete="off" />
<form class="new_user" id="new_user_session_small" action="/users/login?return_to=%2Fseries%2F1728802%3Fview_adult%3Dtrue" accept-charset="UTF-8" method="post"><input type="hidden" name="authenticity_token" value="DXfrKgMV6-0E5FOhkZJyCxyWcxj0vxN9-z47_EFjmI0xhK1Wc_PCP6I8OXGdxyOz_UG1YEv3YjuvOwGIGww_VQ" autocomplete="off" />
<dl>
<dt><label for="user_session_login_small">Username or email:</label></dt>
<dd><input autocomplete="on" id="user_session_login_small" type="text" name="user[login]" /></dd>
Expand Down Expand Up @@ -138,7 +138,7 @@ <h1 class="heading">
<p>
<label class="landmark" for="site_search">Work Search</label>
<input class="text" id="site_search" aria-describedby="site_search_tooltip" type="text" name="work_search[query]" />
<span class="tip" role="tooltip" id="site_search_tooltip">tip: arthur merlin words&gt;1000 sort:hits</span>
<span class="tip" role="tooltip" id="site_search_tooltip">tip: &quot;uchiha sasuke/uzumaki naruto&quot; angst kudos&gt;10</span>
<span class="submit actions"><input type="submit" value="Search" class="button" /></span>
</p>
</fieldset>
Expand Down Expand Up @@ -281,13 +281,13 @@ <h6 class="landmark heading">Series</h6>
<dd class="comments"><a href="/works/23977417?show_comments=true#comments">27</a></dd>

<dt class="kudos">Kudos:</dt>
<dd class="kudos"><a href="/works/23977417#kudos">573</a></dd>
<dd class="kudos"><a href="/works/23977417#kudos">585</a></dd>

<dt class="bookmarks">Bookmarks:</dt>
<dd class="bookmarks"><a href="/works/23977417/bookmarks">83</a></dd>
<dd class="bookmarks"><a href="/works/23977417/bookmarks">81</a></dd>

<dt class="hits">Hits:</dt>
<dd class="hits">8,455</dd>
<dd class="hits">8,626</dd>

</dl>

Expand Down Expand Up @@ -363,13 +363,13 @@ <h6 class="landmark heading">Series</h6>
<dd class="comments"><a href="/works/24121558?show_comments=true#comments">20</a></dd>

<dt class="kudos">Kudos:</dt>
<dd class="kudos"><a href="/works/24121558#kudos">437</a></dd>
<dd class="kudos"><a href="/works/24121558#kudos">449</a></dd>

<dt class="bookmarks">Bookmarks:</dt>
<dd class="bookmarks"><a href="/works/24121558/bookmarks">102</a></dd>
<dd class="bookmarks"><a href="/works/24121558/bookmarks">104</a></dd>

<dt class="hits">Hits:</dt>
<dd class="hits">8,224</dd>
<dd class="hits">8,508</dd>

</dl>

Expand Down Expand Up @@ -400,10 +400,10 @@ <h5 class="fandoms heading">

<!--required tags-->
<ul class="required-tags">
<li> <a class="help symbol question modal" aria-label="Symbols key" href="/help/symbols-key.html"><span class="rating-teen rating" title="Teen And Up Audiences"><span class="text">Teen And Up Audiences</span></span></a></li>
<li> <a class="help symbol question modal" aria-label="Symbols key" href="/help/symbols-key.html"><span class="warning-yes warnings" title="Rape/Non-Con"><span class="text">Rape/Non-Con</span></span></a></li>
<li> <a class="help symbol question modal" aria-label="Symbols key" href="/help/symbols-key.html"><span class="category-gen category" title="Gen"><span class="text">Gen</span></span></a></li>
<li> <a class="help symbol question modal" aria-label="Symbols key" href="/help/symbols-key.html"><span class="complete-yes iswip" title="Complete Work"><span class="text">Complete Work</span></span></a></li>
<li><a class="help symbol question modal" title="Symbols key" href="/help/symbols_key"><span class="rating-teen rating" title="Teen And Up Audiences"><span class="text">Teen And Up Audiences</span></span></a></li>
<li><a class="help symbol question modal" title="Symbols key" href="/help/symbols_key"><span class="warning-yes warnings" title="Rape/Non-Con"><span class="text">Rape/Non-Con</span></span></a></li>
<li><a class="help symbol question modal" title="Symbols key" href="/help/symbols_key"><span class="category-gen category" title="Gen"><span class="text">Gen</span></span></a></li>
<li><a class="help symbol question modal" title="Symbols key" href="/help/symbols_key"><span class="complete-yes iswip" title="Complete Work"><span class="text">Complete Work</span></span></a></li>
</ul>
<p class="datetime">08 Jun 2020</p>
</div>
Expand Down Expand Up @@ -445,13 +445,13 @@ <h6 class="landmark heading">Series</h6>
<dd class="comments"><a href="/works/24597715?show_comments=true#comments">17</a></dd>

<dt class="kudos">Kudos:</dt>
<dd class="kudos"><a href="/works/24597715#kudos">1,139</a></dd>
<dd class="kudos"><a href="/works/24597715#kudos">1,173</a></dd>

<dt class="bookmarks">Bookmarks:</dt>
<dd class="bookmarks"><a href="/works/24597715/bookmarks">119</a></dd>
<dd class="bookmarks"><a href="/works/24597715/bookmarks">122</a></dd>

<dt class="hits">Hits:</dt>
<dd class="hits">11,909</dd>
<dd class="hits">12,211</dd>

</dl>

Expand Down Expand Up @@ -492,13 +492,14 @@ <h4 class="heading">Contact Us</h4>
<ul class="menu">
<li><a href="/abuse_reports/new">Policy Questions &amp; Abuse Reports</a></li>
<li><a href="/support">Technical Support &amp; Feedback</a></li>
<li><a href="/donate">Donate or Volunteer</a></li>
</ul>
</li>
<li class="module group">
<h4 class="heading">Development</h4>
<ul class="menu">
<li>
<a href="https://github.com/otwcode/otwarchive/commits/v1.0.0.1">otwarchive v1.0.0.1</a>
<a href="https://github.com/otwcode/otwarchive/commits/v1.8.0.3">otwarchive v1.8.0.3</a>
</li>
<li><a href="/known_issues">Known Issues</a></li>
<li>
Expand Down Expand Up @@ -588,5 +589,5 @@ <h4 class="heading">Development</h4>
</script>


<script>(function(){function c(){var b=a.contentDocument||a.contentWindow.document;if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a19c0ee918b9d883',t:'MTc4MzgxNzQ3Mw=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
<script>(function(){function c(){var b=a.contentDocument||(a.contentWindow&&a.contentWindow.document);if(b){var d=b.createElement('script');d.innerHTML="window.__CF$cv$params={r:'a46339563b5dad81',t:'MTc5MTI3NDU4Ng=='};var a=document.createElement('script');a.src='/cdn-cgi/challenge-platform/scripts/jsd/main.js';document.getElementsByTagName('head')[0].appendChild(a);";b.getElementsByTagName('head')[0].appendChild(d)}}if(document.body){var a=document.createElement('iframe');a.height=1;a.width=1;a.style.position='absolute';a.style.top=0;a.style.left=0;a.style.border='none';a.style.visibility='hidden';document.body.appendChild(a);if('loading'!==document.readyState)c();else if(window.addEventListener)document.addEventListener('DOMContentLoaded',c);else{var e=document.onreadystatechange||function(){};document.onreadystatechange=function(b){e(b);'loading'!==document.readyState&&(document.onreadystatechange=e,c())}}}})();</script></body>
</html>
Loading
Loading