Compare commits
1
Commits
d40f8b3d0f
..
dev
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ae7664fbe0 |
@@ -302,6 +302,10 @@ export async function hydrateMintGrid(options: HydrateOptions): Promise<void> {
|
||||
|
||||
const all = await fetchListing(type);
|
||||
if (!all) return;
|
||||
// The reader may have navigated away while the fetch was in flight. The router has
|
||||
// already swapped this grid out of the document, so rendering into it (and telling the
|
||||
// caller to re-apply filters against cards nobody can see) is wasted work.
|
||||
if (!grid.isConnected) return;
|
||||
const items = limit === undefined ? all : all.slice(0, limit);
|
||||
|
||||
const cards = renderMintGrid(grid, items, {
|
||||
|
||||
@@ -138,7 +138,7 @@ const schema = [
|
||||
)
|
||||
}
|
||||
|
||||
<div class="mint-grid" data-mint-grid>
|
||||
<div class="mint-grid" data-mint-grid="fedimint">
|
||||
{federations.map((federation, i) => <MintCard mint={federation} rank={i + 1} />)}
|
||||
</div>
|
||||
|
||||
@@ -259,7 +259,7 @@ const schema = [
|
||||
|
||||
const t = useI18n();
|
||||
|
||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid]');
|
||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid="fedimint"]');
|
||||
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
||||
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
||||
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
||||
|
||||
@@ -142,7 +142,7 @@ const schema = [
|
||||
{t('lnurlMints.showing', { total: mints.length, online, offline })}
|
||||
</p>
|
||||
|
||||
<div class="mint-grid" data-mint-grid>
|
||||
<div class="mint-grid" data-mint-grid="lnurl">
|
||||
{mints.map((mint, i) => (
|
||||
<MintCard
|
||||
mint={mint}
|
||||
@@ -272,7 +272,7 @@ const schema = [
|
||||
// `t` formats its own numbers, so the grouping separator follows the locale too.
|
||||
const t = useI18n();
|
||||
|
||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid]');
|
||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid="lnurl"]');
|
||||
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
||||
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
||||
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
||||
|
||||
@@ -125,7 +125,7 @@ const schema = [
|
||||
{t('mints.showing', { total: mints.length, online, offline })}
|
||||
</p>
|
||||
|
||||
<div class="mint-grid" data-mint-grid>
|
||||
<div class="mint-grid" data-mint-grid="cashu">
|
||||
{mints.map((mint, i) => <MintCard mint={mint} rank={i + 1} capabilities={mint.capabilities} />)}
|
||||
</div>
|
||||
|
||||
@@ -244,7 +244,7 @@ const schema = [
|
||||
// `t` formats its own numbers, so the grouping separator follows the locale too.
|
||||
const t = useI18n();
|
||||
|
||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid]');
|
||||
const grid = document.querySelector<HTMLElement>('[data-mint-grid="cashu"]');
|
||||
const searchInput = document.querySelector<HTMLInputElement>('[data-search-input]');
|
||||
const sortSelect = document.querySelector<HTMLSelectElement>('[data-sort]');
|
||||
const hideOffline = document.querySelector<HTMLButtonElement>('[data-hide-offline]');
|
||||
|
||||
@@ -208,6 +208,15 @@ export function armSharedTransitions(): void {
|
||||
* only does its work at module scope is dead after the first navigation. It does fire
|
||||
* `astro:page-load` on every arrival, including the first, but on the first it waits
|
||||
* for `window.load`, which is far too late to be the only trigger. Hence both.
|
||||
*
|
||||
* The listener is never removed, on purpose: a layout island (the language switcher,
|
||||
* the login dialog) has DOM on every page, and this is what keeps it alive. The flip
|
||||
* side is that `setup` runs on *every* page the router swaps in, not only pages that
|
||||
* include the script. A page-level script must therefore look its DOM up by a marker
|
||||
* unique to that page — `data-mint-grid="cashu"`, `data-home-grid="fedimint"` — and do
|
||||
* nothing when the marker is absent. A marker shared between pages means a stale page's
|
||||
* setup finds the current page's DOM and rewrites it: the listing pages once flashed
|
||||
* back to the previous ecosystem's mints for exactly that reason.
|
||||
*/
|
||||
export function onReady(setup: () => void): void {
|
||||
let done = false;
|
||||
|
||||
@@ -0,0 +1,60 @@
|
||||
/**
|
||||
* The three listing pages must each own their grid.
|
||||
*
|
||||
* `<ClientRouter />` swaps pages without reloading, and `onReady` in `scripts/reveal.ts`
|
||||
* re-runs every page's setup on every arrival — deliberately, since that is what keeps
|
||||
* the layout islands alive. So once `/mints` has been visited, its setup also runs on
|
||||
* `/fedimints`. If both pages mark their grid the same way, the stale cashu setup finds
|
||||
* the fedimint grid, fetches `?type=cashu` and rewrites it: the page shows fedimints for
|
||||
* a moment, then flashes back to cashu mints. That happened.
|
||||
*
|
||||
* The guard is that each page's marker carries its ecosystem, and its script only looks
|
||||
* for its own. This checks the template, the query and the hydrate call agree, and that
|
||||
* no bare `data-mint-grid` has crept back in. Plain node, source read as text, like
|
||||
* `i18n-wiring.test.mjs`.
|
||||
*/
|
||||
import test from 'node:test';
|
||||
import assert from 'node:assert/strict';
|
||||
import { readFileSync } from 'node:fs';
|
||||
import path from 'node:path';
|
||||
import { fileURLToPath } from 'node:url';
|
||||
|
||||
const pagesDir = path.resolve(path.dirname(fileURLToPath(import.meta.url)), '../src/pages/[...locale]');
|
||||
const read = (file) => readFileSync(path.join(pagesDir, file), 'utf8');
|
||||
|
||||
const LISTINGS = [
|
||||
['mints.astro', 'cashu'],
|
||||
['fedimints.astro', 'fedimint'],
|
||||
['lnurl-mints.astro', 'lnurl'],
|
||||
];
|
||||
|
||||
for (const [file, type] of LISTINGS) {
|
||||
test(`${file} marks, queries and hydrates its grid as ${type}`, () => {
|
||||
const source = read(file);
|
||||
|
||||
assert.match(
|
||||
source,
|
||||
new RegExp(`<div class="mint-grid" data-mint-grid="${type}">`),
|
||||
`${file}: template grid is not marked data-mint-grid="${type}"`,
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
new RegExp(`querySelector<HTMLElement>\\('\\[data-mint-grid="${type}"\\]'\\)`),
|
||||
`${file}: script does not query [data-mint-grid="${type}"]`,
|
||||
);
|
||||
assert.match(
|
||||
source,
|
||||
new RegExp(`type: '${type}',`),
|
||||
`${file}: hydrateMintGrid is not called with type '${type}'`,
|
||||
);
|
||||
|
||||
// A bare marker, in either the template or the query, is the bug coming back.
|
||||
assert.doesNotMatch(source, /data-mint-grid[>\s]/, `${file}: bare data-mint-grid in template`);
|
||||
assert.doesNotMatch(source, /\[data-mint-grid\]/, `${file}: bare [data-mint-grid] selector`);
|
||||
});
|
||||
}
|
||||
|
||||
test('the three listings use three different markers', () => {
|
||||
const types = new Set(LISTINGS.map(([, type]) => type));
|
||||
assert.equal(types.size, LISTINGS.length);
|
||||
});
|
||||
Reference in New Issue
Block a user