(selector));
links.filter((el) => filter(el)).forEach((el) => observer.observe(el));
});
};
return {
start: () => observe(),
stop: () => observer.disconnect(),
update: () => (clear(), observe())
};
}
swup-preload-plugin-df3ac9d/src/queue.ts 0000664 0000000 0000000 00000002251 14717442373 0020506 0 ustar 00root root 0000000 0000000 type QueueFunction = {
(): void;
__queued?: boolean;
};
export type Queue = {
add: (fn: QueueFunction, highPriority?: boolean) => void;
next: () => void;
};
export default function createQueue(limit: number = 1): Queue {
const qlow: QueueFunction[] = [];
const qhigh: QueueFunction[] = [];
let total = 0;
let running = 0;
function add(fn: QueueFunction, highPriority: boolean = false): void {
// Already added before?
if (fn.__queued) {
// Move from low to high-priority queue
if (highPriority) {
const idx = qlow.indexOf(fn);
if (idx >= 0) {
const removed = qlow.splice(idx, 1);
total = total - removed.length;
}
} else {
return;
}
}
// Mark as processed
fn.__queued = true;
// Push to queue: high or low
(highPriority ? qhigh : qlow).push(fn);
// Increment total
total++;
// Initialize queue if first item
if (total <= 1) {
run();
}
}
function next(): void {
running--; // make room for next
run();
}
function run(): void {
if (running < limit && total > 0) {
const fn = qhigh.shift() || qlow.shift() || (() => {});
fn();
total--;
running++; // is now WIP
}
}
return { add, next };
}
swup-preload-plugin-df3ac9d/src/util.ts 0000775 0000000 0000000 00000001626 14717442373 0020347 0 ustar 00root root 0000000 0000000 /**
* Check if the user's connection is configured and fast enough
* to preload data in the background.
*/
export function networkSupportsPreloading(): boolean {
if (navigator.connection) {
if (navigator.connection.saveData) {
return false;
}
if (navigator.connection.effectiveType?.endsWith('2g')) {
return false;
}
}
return true;
}
/**
* Does this device support true hover/pointer interactions?
*/
export function deviceSupportsHover() {
return window.matchMedia('(hover: hover)').matches;
}
/**
* Is this element an anchor element?
*/
export function isAnchorElement(element: unknown): element is HTMLAnchorElement | SVGAElement {
return !!element && (element instanceof HTMLAnchorElement || element instanceof SVGAElement);
}
/**
* Safe requestIdleCallback function that falls back to setTimeout
*/
export const whenIdle = window.requestIdleCallback || ((cb) => setTimeout(cb, 1));
swup-preload-plugin-df3ac9d/tests/ 0000775 0000000 0000000 00000000000 14717442373 0017365 5 ustar 00root root 0000000 0000000 swup-preload-plugin-df3ac9d/tests/config/ 0000775 0000000 0000000 00000000000 14717442373 0020632 5 ustar 00root root 0000000 0000000 swup-preload-plugin-df3ac9d/tests/config/playwright.config.ts 0000664 0000000 0000000 00000005521 14717442373 0024643 0 ustar 00root root 0000000 0000000 import { fileURLToPath } from 'node:url';
import path from 'node:path';
import { defineConfig, devices } from '@playwright/test';
const baseURL = 'http://localhost:8274';
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
/**
* See https://playwright.dev/docs/test-configuration.
*/
export default defineConfig({
/* Run this file before starting the tests */
globalSetup: path.resolve(__dirname, './playwright.setup.ts'),
/* Run this file after all the tests have finished */
// globalTeardown: path.resolve(__dirname, './playwright.teardown.ts'),
/* Directory containing the test files */
testDir: '../functional',
/* Folder for test artifacts: screenshots, videos, ... */
outputDir: '../results',
/* Timeout individual tests after 5 seconds */
timeout: 10_000,
/* Run tests in files in parallel */
fullyParallel: true,
/* Fail the build on CI if you accidentally left test.only in the source code. */
forbidOnly: !!process.env.CI,
/* Retry on CI only */
retries: process.env.CI ? 1 : 0,
/* Limit parallel workers on CI, use default locally. */
workers: process.env.CI ? 1 : undefined,
// Limit the number of failures on CI to save resources
maxFailures: process.env.CI ? 10 : undefined,
/* Reporter to use. See https://playwright.dev/docs/test-reporters */
reporter: process.env.CI
? [['dot'], ['github'], ['json', { outputFile: '../../playwright-results.json' }]]
: [['list'], ['html', { outputFolder: '../reports/html', open: 'on-failure' }]],
expect: {
/* Timeout async expect matchers after 3 seconds */
timeout: 3_000
},
/* Shared settings for all the projects below. See https://playwright.dev/docs/api/class-testoptions. */
use: {
/* Base URL to use in actions like `await page.goto('/')`. */
baseURL,
/* Collect trace when retrying the failed test. See https://playwright.dev/docs/trace-viewer */
trace: 'on-first-retry',
/* Capture screenshot after each test failure. */
screenshot: 'only-on-failure',
/* Capture video if failed tests. */
video: 'retain-on-failure'
},
/* Configure projects for major browsers */
projects: [
{ name: 'chromium', use: { ...devices['Desktop Chrome'] } },
{ name: 'firefox', use: {
...devices['Desktop Firefox'],
launchOptions: {
firefoxUserPrefs: {
// https://github.com/microsoft/playwright/issues/7769#issuecomment-966098074
// Set to support fine pointer: 0x02 = FINE_POINTER; 0x04 = HOVER_CAPABLE_POINTER
'ui.primaryPointerCapabilities': 0x02 | 0x04,
'ui.allPointerCapabilities': 0x02 | 0x04
}
}
}
},
{ name: 'webkit', use: { ...devices['Desktop Safari'] } },
{ name: 'ios', use: { ...devices['iPhone 13'] } }
],
/* Run your local dev server before starting the tests */
webServer: {
url: baseURL,
command: 'npm run test:e2e:serve',
reuseExistingServer: !process.env.CI
}
});
swup-preload-plugin-df3ac9d/tests/config/playwright.setup.ts 0000664 0000000 0000000 00000000443 14717442373 0024534 0 ustar 00root root 0000000 0000000 import { cpSync, rmSync } from 'node:fs';
export default () => {
rmSync('./tests/fixtures/dist/', { recursive: true, force: true });
cpSync('./dist/', `./tests/fixtures/dist/`, { recursive: true });
cpSync('./node_modules/swup/dist/Swup.umd.js', `./tests/fixtures/dist/swup.umd.js`);
};
swup-preload-plugin-df3ac9d/tests/config/playwright.teardown.ts 0000664 0000000 0000000 00000000165 14717442373 0025220 0 ustar 00root root 0000000 0000000 import { rmSync } from 'node:fs';
export default () => {
rmSync('./tests/fixtures/dist/', { recursive: true });
};
swup-preload-plugin-df3ac9d/tests/config/serve.json 0000775 0000000 0000000 00000000225 14717442373 0022653 0 ustar 00root root 0000000 0000000 {
"public": "tests/fixtures",
"cleanUrls": false,
"redirects": [
{ "source": "/redirect-2.html", "destination": "/redirect-3.html" }
]
}
swup-preload-plugin-df3ac9d/tests/config/vitest.config.ts 0000664 0000000 0000000 00000000434 14717442373 0023765 0 ustar 00root root 0000000 0000000 /**
* Vitest config file
* @see https://vitest.dev/config/
*/
import { defineConfig } from 'vitest/config';
export default defineConfig({
test: {
environment: 'jsdom',
include: [
'tests/unit/**/*.test.ts'
],
setupFiles: [
'tests/config/vitest.setup.ts'
]
}
});
swup-preload-plugin-df3ac9d/tests/config/vitest.setup.ts 0000664 0000000 0000000 00000000426 14717442373 0023661 0 ustar 00root root 0000000 0000000 import { vi } from 'vitest';
/**
* Allow spying on the console
*/
// vi.spyOn(console, 'log');
// vi.spyOn(console, 'warn');
// vi.spyOn(console, 'error');
// Stub browser functions for vitest
// console.log = vi.fn();
// console.warn = vi.fn();
// console.error = vi.fn();
swup-preload-plugin-df3ac9d/tests/fixtures/ 0000775 0000000 0000000 00000000000 14717442373 0021236 5 ustar 00root root 0000000 0000000 swup-preload-plugin-df3ac9d/tests/fixtures/assets/ 0000775 0000000 0000000 00000000000 14717442373 0022540 5 ustar 00root root 0000000 0000000 swup-preload-plugin-df3ac9d/tests/fixtures/assets/style.css 0000664 0000000 0000000 00000000541 14717442373 0024412 0 ustar 00root root 0000000 0000000 *, *:before, *:after {
box-sizing: border-box;
}
:root {
--red: hsl(0, 74%, 67%);
--green: hsl(100, 68%, 58%);
--blue: oklch(59.12% 0.153 230.82);
}
body {
font-family: system-ui;
padding: 1rem;
max-width: 800px;
margin: 5% auto;
}
h1, h2, ul {
margin-bottom: 1rem;
}
:focus-visible {
box-shadow: 0 0 0 3px gold;
outline: 0;
}
swup-preload-plugin-df3ac9d/tests/fixtures/link-selector-default.html 0000664 0000000 0000000 00000001635 14717442373 0026326 0 ustar 00root root 0000000 0000000
Link types
Link types
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
SVG link
swup-preload-plugin-df3ac9d/tests/fixtures/link-selector-modified.html 0000664 0000000 0000000 00000001705 14717442373 0026460 0 ustar 00root root 0000000 0000000
Link types
Link types
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
SVG link
swup-preload-plugin-df3ac9d/tests/fixtures/origins.html 0000664 0000000 0000000 00000001601 14717442373 0023574 0 ustar 00root root 0000000 0000000
Origins
Origins
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/page-1.html 0000664 0000000 0000000 00000001503 14717442373 0023175 0 ustar 00root root 0000000 0000000
Page 1
Page 1
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/page-2.html 0000664 0000000 0000000 00000001503 14717442373 0023176 0 ustar 00root root 0000000 0000000
Page 2
Page 2
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/page-3.html 0000664 0000000 0000000 00000001503 14717442373 0023177 0 ustar 00root root 0000000 0000000
Page 3
Page 3
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/preload-attributes.html 0000664 0000000 0000000 00000001607 14717442373 0025742 0 ustar 00root root 0000000 0000000
Preload initial page
Preload initial page
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/preload-initial-disabled.html 0000664 0000000 0000000 00000001622 14717442373 0026747 0 ustar 00root root 0000000 0000000
Preload initial page
Preload initial page
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/throttle.html 0000664 0000000 0000000 00000001554 14717442373 0023776 0 ustar 00root root 0000000 0000000
Throttle
Throttle
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/visible-links-options.html 0000664 0000000 0000000 00000002723 14717442373 0026374 0 ustar 00root root 0000000 0000000
Preload visible links with options
Preload visible links with options
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/visible-links-selector.html 0000664 0000000 0000000 00000002355 14717442373 0026522 0 ustar 00root root 0000000 0000000
Preload visible links with options
Preload visible links with modified selector
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/fixtures/visible-links.html 0000664 0000000 0000000 00000002344 14717442373 0024702 0 ustar 00root root 0000000 0000000
Preload visible links
Preload visible links
Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore et dolore magna aliquyam erat, sed diam voluptua.
swup-preload-plugin-df3ac9d/tests/functional/ 0000775 0000000 0000000 00000000000 14717442373 0021527 5 ustar 00root root 0000000 0000000 swup-preload-plugin-df3ac9d/tests/functional/inc/ 0000775 0000000 0000000 00000000000 14717442373 0022300 5 ustar 00root root 0000000 0000000 swup-preload-plugin-df3ac9d/tests/functional/inc/commands.ts 0000664 0000000 0000000 00000005540 14717442373 0024455 0 ustar 00root root 0000000 0000000 import { expect, Page } from '@playwright/test';
import type Swup from 'swup';
declare global {
interface Window {
_swup: Swup;
data: any;
}
}
export async function waitForSwup(page: Page) {
await page.waitForSelector('html.swup-enabled');
}
export function sleep(timeout = 0): Promise {
return new Promise((resolve) => setTimeout(() => resolve(undefined), timeout));
}
export async function clickOnLink(page: Page, url: string, options?: Parameters[1]) {
await page.click(`a[href="${url}"]`, options);
await expectToBeAt(page, url);
}
export async function scroll(page: Page, { direction = 'down', delay = 10 }: { direction?: 'down' | 'up', delay?: number } = {}) {
await page.evaluate(async ({ direction, delay }) => {
const scrollHeight = () => document.body.scrollHeight;
const sleep = (ms: number) => new Promise(resolve => setTimeout(resolve, ms));
const start = direction === 'down' ? 0 : scrollHeight();
const shouldStop = (position: number) => direction === 'down' ? position > scrollHeight() : position < 0;
const increment = direction === 'down' ? 100 : -100;
console.error(start, shouldStop(start), increment);
for (let i = start; !shouldStop(i); i += increment) {
window.scrollTo(0, i);
await sleep(delay);
}
}, { direction, delay });
}
export async function scrollTo(page: Page, y: number | string) {
await page.evaluate(async (y) => {
if (typeof y === 'string') {
y = document.querySelector(y)?.offsetTop ?? 0
}
window.scrollTo(0, y);
}, y);
}
export async function navigateWithSwup(
page: Page,
url: string,
options?: Parameters[1]
) {
await page.evaluate(
({ url, options }) => window._swup.navigate(url, options),
{ url, options }
);
await expectToBeAt(page, url);
}
export async function expectToBeAt(page: Page, url: string, title?: string) {
await expect(page).toHaveURL(url);
if (title) {
await expect(page, `Expected title: ${title}`).toHaveTitle(title);
await expect(page.locator('h1'), `Expected h1: ${title}`).toContainText(title);
}
}
export async function expectSwupToHaveCacheEntry(page: Page, url: string) {
const exists = () => page.evaluate((url) => window._swup.cache.has(url), url);
await expect(async () => expect(await exists(), `Expected ${url} to be in cache`).toBe(true)).toPass();
}
export async function expectSwupNotToHaveCacheEntry(page: Page, url: string) {
const exists = () => page.evaluate((url) => window._swup.cache.has(url), url);
expect(await exists(), `Expected ${url} not to be in cache`).toBe(false);
}
export async function expectSwupToHaveCacheEntries(page: Page, urls: string[]) {
for (const url of urls) {
await expectSwupToHaveCacheEntry(page, url);
}
}
export async function expectSwupNotToHaveCacheEntries(page: Page, urls: string[]) {
for (const url of urls) {
await expectSwupNotToHaveCacheEntry(page, url);
}
}
swup-preload-plugin-df3ac9d/tests/functional/preload-plugin.spec.ts 0000664 0000000 0000000 00000024175 14717442373 0025763 0 ustar 00root root 0000000 0000000 import { test, expect } from '@playwright/test';
import {
sleep,
clickOnLink,
waitForSwup,
navigateWithSwup,
expectSwupToHaveCacheEntry,
expectSwupNotToHaveCacheEntry,
expectSwupToHaveCacheEntries,
expectSwupNotToHaveCacheEntries,
scroll,
scrollTo
} from './inc/commands.js';
test.describe('instance methods', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/page-1.html');
await waitForSwup(page);
});
test('adds preload methods on swup instance', async ({ page }) => {
expect(await page.evaluate(() => typeof window._swup.preload)).toBe('function');
expect(await page.evaluate(() => typeof window._swup.preloadLinks)).toBe('function');
});
test('allows preloading individual page', async ({ page }) => {
await page.evaluate(() => window._swup.preload!('/page-2.html'));
await expectSwupToHaveCacheEntry(page, '/page-2.html');
await expectSwupNotToHaveCacheEntry(page, '/page-3.html');
});
test('allows preloading multiple pages', async ({ page }) => {
await page.evaluate(() => window._swup.preload!(['/page-2.html', '/page-3.html']));
await expectSwupToHaveCacheEntry(page, '/page-2.html');
await expectSwupToHaveCacheEntry(page, '/page-3.html');
});
test('returns the cache entry if already preloaded', async ({ page }) => {
const result = await page.evaluate(async () => {
await window._swup.preload!('/page-2.html');
const second = window._swup.preload!('/page-2.html');
return second;
});
expect(result).toHaveProperty('url');
expect(result).toHaveProperty('html');
expect(typeof result.url).toBe('string');
expect(typeof result.html).toBe('string');
});
test('returns the preload promise if currently preloading', async ({ page }) => {
const result = await page.evaluate(async () => {
window._swup.preload!('/page-2.html');
const second = await window._swup.preload!('/page-2.html');
return second;
});
expect(result).toHaveProperty('url');
expect(result).toHaveProperty('html');
expect(typeof result.url).toBe('string');
expect(typeof result.html).toBe('string');
});
});
test.describe('preload attributes', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/preload-attributes.html');
await waitForSwup(page);
});
test('preloads links with data-swup-preload attributes', async ({ page }) => {
await expectSwupToHaveCacheEntry(page, '/page-1.html');
await expectSwupToHaveCacheEntry(page, '/page-3.html');
await expectSwupNotToHaveCacheEntry(page, '/page-2.html');
});
});
test.describe('initial page', () => {
test('preloads initial page', async ({ page }) => {
await page.goto('/page-1.html');
await waitForSwup(page);
await expectSwupToHaveCacheEntry(page, '/page-1.html');
});
test('allows disabling initial page preload', async ({ page }) => {
await page.goto('/preload-initial-disabled.html');
await waitForSwup(page);
await sleep(500);
await expectSwupNotToHaveCacheEntry(page, '/preload-initial-disabled.html');
});
});
test.describe('active links', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/page-1.html');
await waitForSwup(page);
});
test('preloads links on focus', async ({ page }) => {
await expectSwupNotToHaveCacheEntry(page, '/page-2.html');
await page.focus('a[href="/page-2.html"]');
await expectSwupToHaveCacheEntry(page, '/page-2.html');
});
test('preloads links on hover', async ({ page, isMobile }) => {
test.skip(isMobile, 'test hover on desktop only');
await expectSwupNotToHaveCacheEntry(page, '/page-2.html');
await page.hover('a[href="/page-2.html"]');
await expectSwupToHaveCacheEntry(page, '/page-2.html');
});
test('preloads links on touchstart', async ({ page, isMobile }) => {
test.skip(!isMobile, 'test touch on mobile only');
await page.evaluate(() => {
// Rewrite url to make sure the cached page is from preloading
window._swup.hooks.on('visit:start', (visit) => (visit.to.url = '/page-3.html'));
});
await expectSwupNotToHaveCacheEntry(page, '/page-2.html');
await page.tap('a[href="/page-2.html"]');
await expectSwupToHaveCacheEntry(page, '/page-2.html');
await expectSwupToHaveCacheEntry(page, '/page-3.html');
});
test('respects swup link selector', async ({ page }) => {
await page.goto('/link-selector-default.html');
await waitForSwup(page);
await page.focus('svg a');
await sleep(200);
await expectSwupNotToHaveCacheEntry(page, '/page-2.html');
});
test('allows modified swup link selector', async ({ page }) => {
await page.goto('/link-selector-modified.html');
await waitForSwup(page);
await page.focus('svg a');
await expectSwupToHaveCacheEntry(page, '/page-2.html');
});
});
test.describe('visible links', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/visible-links.html');
});
test('does not by default preload links initially in view', async ({ page }) => {
await page.goto('/page-1.html');
await sleep(200);
await expectSwupNotToHaveCacheEntries(page, ['/page-2.html', '/page-3.html']);
});
test('preloads links initially in view', async ({ page }) => {
await expectSwupNotToHaveCacheEntries(page, [
'/page-1.html',
'/page-2.html',
'/page-3.html'
]);
// await sleep(500);
await expectSwupToHaveCacheEntries(page, ['/page-1.html', '/page-2.html']);
await expectSwupNotToHaveCacheEntries(page, ['/page-3.html', '/page-4.html']);
});
test('preloads links after viewport change', async ({ page }) => {
await expectSwupToHaveCacheEntries(page, ['/page-1.html', '/page-2.html']);
await expectSwupNotToHaveCacheEntries(page, ['/page-3.html', '/page-4.html']);
// Scroll page link #3 into view
await scrollTo(page, 'a[href="/page-3.html"]');
await expectSwupToHaveCacheEntries(page, ['/page-3.html', '/page-4.html']);
});
test('skips links when scrolling by quickly', async ({ page }) => {
await expectSwupToHaveCacheEntries(page, ['/page-1.html', '/page-2.html']);
await expectSwupNotToHaveCacheEntries(page, ['/page-3.html', '/page-9.html']);
// Scroll down quickly, skipping links in the middle
await scroll(page, { direction: 'down', delay: 10 });
await expectSwupToHaveCacheEntries(page, ['/page-9.html']);
await expectSwupNotToHaveCacheEntries(page, ['/page-5.html']);
});
test('preloads links when scrolling by slowly', async ({ page }) => {
await expectSwupToHaveCacheEntries(page, ['/page-1.html', '/page-2.html']);
await expectSwupNotToHaveCacheEntries(page, ['/page-3.html', '/page-9.html']);
// Scroll down slowly, preloading links in the middle
await scroll(page, { direction: 'down', delay: 100 });
await expectSwupToHaveCacheEntries(page, [
'/page-3.html',
'/page-6.html',
'/page-8.html',
'/page-9.html'
]);
});
test('allows configuring options', async ({ page }) => {
await page.goto('/visible-links-options.html');
await expectSwupToHaveCacheEntries(page, ['/page-1.html']);
// Scroll down quickly, still preloading in the middle
await scroll(page, { direction: 'down', delay: 10 });
await expectSwupToHaveCacheEntries(page, ['/page-3.html', '/page-6.html', '/page-8.html']);
await expectSwupNotToHaveCacheEntries(page, ['/page-2.html']); // ignored via ignore()
await expectSwupNotToHaveCacheEntries(page, ['/page-9.html']); // ignored via containers
});
test('respects swup link selector', async ({ page }) => {
await page.goto('/visible-links-selector.html');
// await scroll(page, { direction: 'down', delay: 10 });
await sleep(200);
await expectSwupToHaveCacheEntries(page, ['/page-1.html', '/page-3.html', '/page-5.html']);
await expectSwupNotToHaveCacheEntries(page, ['/page-2.html', '/page-4.html']); // ignored via linkSelector option
});
});
test.describe('throttle', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/throttle.html');
await waitForSwup(page);
});
test('limits number of concurrent requests', async ({ page }) => {
await page.evaluate(() => {
window.data = {
count: 0,
max: 0,
inc() {
this.count++;
this.max = Math.max(this.max, this.count);
},
dec() {
this.count--;
}
};
window._swup.hooks.before('page:preload', () => window.data.inc());
window._swup.hooks.on('page:preload', () => window.data.dec());
window._swup.preload!([1, 2, 3, 4, 5, 6, 7, 8].map((n) => `/page-${n}.html`));
});
await page.focus('a[href="/page-2.html"]');
const max = () => page.evaluate(() => window.data.max);
await expect(async () => expect(await max()).toBe(4)).toPass();
});
});
test.describe('hooks', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/page-1.html');
await waitForSwup(page);
});
test('triggers page:preload hook on focus', async ({ page }) => {
await page.evaluate(() => {
window._swup.hooks.on('page:preload', () => (window.data = true));
});
await page.focus('a[href="/page-2.html"]');
const triggered = () => page.evaluate(() => window.data);
await expect(async () => expect(await triggered()).toBe(true)).toPass();
});
test('triggers page:preload hook from API', async ({ page }) => {
await page.evaluate(() => {
window._swup.hooks.on('page:preload', () => (window.data = true));
window._swup.preload!('/page-2.html');
});
const triggered = () => page.evaluate(() => window.data);
await expect(async () => expect(await triggered()).toBe(true)).toPass();
});
});
test.describe('ignores external origins', () => {
test.beforeEach(async ({ page }) => {
await page.goto('/origins.html');
await waitForSwup(page);
await page.evaluate(() => {
window.data = [];
window._swup.hooks.before('page:preload', (visit, { url }) => window.data.push(url));
});
});
test('ignores link elements with external origin', async ({ page }) => {
await page.focus('a[href$="/page-1.html"]');
await page.focus('a[href$="/page-2.html"]');
await page.focus('a[href$="/page-3.html"]');
const urls = await page.evaluate(() => window.data);
expect(urls).toEqual(['/page-1.html', '/page-2.html']);
});
test('ignores preload requests with external origin', async ({ page }) => {
await page.evaluate(() => {
window._swup.preload!([
'https://example.net/page-3.html',
'/page-1.html',
'page-2.html'
]);
});
const urls = await page.evaluate(() => window.data);
expect(urls).toEqual(['/page-1.html', '/page-2.html']);
});
});
swup-preload-plugin-df3ac9d/tests/support/ 0000775 0000000 0000000 00000000000 14717442373 0021101 5 ustar 00root root 0000000 0000000 swup-preload-plugin-df3ac9d/tests/support/utils.ts 0000664 0000000 0000000 00000000774 14717442373 0022621 0 ustar 00root root 0000000 0000000 export function sleep(ms: number): Promise {
return new Promise(r => setTimeout(r, ms));
}
export function createTimer(): () => number {
const start = process.hrtime();
return () => realtimeToMs(process.hrtime(start));
}
export function realtimeToMs([seconds, milliseconds]: [number, number]): number {
return Math.round(seconds * 1000 + milliseconds / 1000000);
}
export function pad(num: number, padding: number = 0.1): [number, number] {
return [num * (1 - padding), num * (1 + padding)];
}
swup-preload-plugin-df3ac9d/tests/unit/ 0000775 0000000 0000000 00000000000 14717442373 0020344 5 ustar 00root root 0000000 0000000 swup-preload-plugin-df3ac9d/tests/unit/queue.test.ts 0000664 0000000 0000000 00000005701 14717442373 0023021 0 ustar 00root root 0000000 0000000 import { describe, expect, it } from 'vitest';
import createQueue from '../../src/queue.js';
import { createTimer, pad, sleep } from '../support/utils.js';
describe('export', () => {
it('exports a function', () => {
expect(createQueue).to.be.a('function');
});
});
describe('return', () => {
it('creates a Queue object', () => {
const queue = createQueue();
expect(queue).to.be.an('object');
expect(queue.add).to.be.a('function');
expect(queue.next).to.be.a('function');
});
});
describe('sequencing', () => {
it('runs callbacks sequentially', async () => {
let size = 1;
let items = 5;
let duration = 100;
let elapsed = 0;
const { add, next } = createQueue(size);
const timer = createTimer();
const test = () => sleep(duration).then(() => (elapsed = timer())).then(next);
for (let i = 0; i < items; i++) {
add(() => test()); // add different function with each iteration = sequential
}
await sleep((items + 1) * duration);
const [min, max] = pad(items * duration);
expect(elapsed).to.be.gte(min);
expect(elapsed).to.be.lte(max);
});
it('debounces repeated calls', async () => {
let size = 1;
let items = 5;
let duration = 100;
let elapsed = 0;
const { add, next } = createQueue(size);
const timer = createTimer();
const test = () => sleep(duration).then(() => (elapsed = timer())).then(next);
for (let i = 0; i < items; i++) {
add(test); // add identical function multiple times = ignore/debounce
}
await sleep((items + 1) * duration);
const [min, max] = pad(duration);
expect(elapsed).to.be.gte(min);
expect(elapsed).to.be.lte(max);
});
it('allows custom queue size', async () => {
let size = 5;
let items = 5;
let duration = 100;
let elapsed = 0;
const { add, next } = createQueue(size);
const timer = createTimer();
const test = () => sleep(duration).then(() => (elapsed = timer())).then(next);
for (let i = 0; i < items; i++) {
add(() => test()); // add different function with each iteration = sequential
}
await sleep((items + 1) * duration);
const [min, max] = pad(items * duration / size);
expect(elapsed).to.be.gte(min);
expect(elapsed).to.be.lte(max);
});
});
describe('priorization', () => {
it('prioritizes high-priority items', async () => {
let size = 1;
let items = 3;
let duration = 100;
let elapsed = 0;
const seen: number[] = [];
const { add, next } = createQueue(size);
const timer = createTimer();
const testLow = () => sleep(duration).then(() => ((seen.push(0), (elapsed = timer())))).then(next);
const testHigh = () => sleep(duration).then(() => ((seen.push(1), (elapsed = timer())))).then(next);
for (let i = 0; i < items; i++) {
add(() => testLow());
}
for (let i = 0; i < items; i++) {
add(() => testHigh(), true);
}
await sleep((2 * items + 1) * duration);
const [min, max] = pad(2 * items * duration);
expect(elapsed).to.be.gte(min);
expect(elapsed).to.be.lte(max);
expect(seen).toEqual([0, 1, 1, 1, 0, 0]);
});
});
swup-preload-plugin-df3ac9d/tsconfig.json 0000664 0000000 0000000 00000000656 14717442373 0020741 0 ustar 00root root 0000000 0000000 {
"include": ["src"],
"compilerOptions": {
"target": "es2022",
"module": "NodeNext",
"moduleResolution": "NodeNext",
"rootDirs": ["./src"],
"resolveJsonModule": true,
"allowJs": true,
"outDir": "./dist",
"esModuleInterop": true,
"allowSyntheticDefaultImports": true,
"strict": true,
"noImplicitAny": true,
"types": [
"./node_modules/network-information-types"
]
}
}