Plausible gives each new site its own script and an init call instead of the generic script with data-domain. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01V9NnLxnPp8vaaurb3Z1MFy
131 lines
5.0 KiB
PHP
131 lines
5.0 KiB
PHP
<?php
|
|
|
|
/*
|
|
* website/ is uploaded by hand as it is, so these guard what a broken upload would show: files that
|
|
* are not there, requests to other hosts, and claims the application does not keep.
|
|
*/
|
|
|
|
/**
|
|
* The site's HTML pages, by name.
|
|
*
|
|
* @return array<string, string>
|
|
*/
|
|
function websitePages(): array
|
|
{
|
|
return [
|
|
'index.html' => file_get_contents(base_path('website/index.html')),
|
|
'privacy.html' => file_get_contents(base_path('website/privacy.html')),
|
|
];
|
|
}
|
|
|
|
/**
|
|
* Every URL an HTML document refers to in `src`, `href`, `srcset` and the gallery's `data-*-src` and
|
|
* `data-*-srcset`, with each srcset entry on its own.
|
|
*
|
|
* @return list<string>
|
|
*/
|
|
function websiteReferences(string $html): array
|
|
{
|
|
preg_match_all('/\s(src|href|srcset|data-[a-z]+-src|data-[a-z]+-srcset)="([^"]*)"/', $html, $matches, PREG_SET_ORDER);
|
|
|
|
return collect($matches)
|
|
->flatMap(fn (array $match): array => str_ends_with($match[1], 'srcset')
|
|
? array_map(fn (string $candidate): string => strtok(trim($candidate), ' '), explode(',', $match[2]))
|
|
: [$match[2]])
|
|
->map(fn (string $url): string => html_entity_decode($url))
|
|
->values()
|
|
->all();
|
|
}
|
|
|
|
test('every file the pages, their stylesheets and the README refer to exists', function () {
|
|
$missing = [];
|
|
|
|
foreach (websitePages() as $page => $html) {
|
|
foreach (websiteReferences($html) as $url) {
|
|
if (preg_match('/^(#|https?:|mailto:)/', $url) === 1) {
|
|
continue;
|
|
}
|
|
|
|
if (! is_file(base_path('website/'.strtok($url, '#?')))) {
|
|
$missing[] = "{$page}: {$url}";
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (glob(base_path('website/css/*.css')) as $stylesheet) {
|
|
preg_match_all('/url\([\'"]?([^\'")]+)[\'"]?\)/', file_get_contents($stylesheet), $matches);
|
|
|
|
foreach ($matches[1] as $url) {
|
|
if (! str_starts_with($url, 'data:') && ! is_file(dirname($stylesheet).'/'.strtok($url, '#?'))) {
|
|
$missing[] = basename($stylesheet).": {$url}";
|
|
}
|
|
}
|
|
}
|
|
|
|
foreach (websiteReferences(file_get_contents(base_path('README.md'))) as $url) {
|
|
if (str_starts_with($url, 'website/') && ! is_file(base_path($url))) {
|
|
$missing[] = "README.md: {$url}";
|
|
}
|
|
}
|
|
|
|
expect($missing)->toBe([]);
|
|
});
|
|
|
|
test('the screenshots are all there, for both themes and both widths', function () {
|
|
$expected = collect([
|
|
'desktop' => ['01-upload', '02-share-created', '03-qr-code', '04-download', '05-admin-dashboard', '06-admin-settings'],
|
|
'phone' => ['01-upload', '02-password', '03-download', '04-qr-code'],
|
|
])->flatMap(fn (array $names, string $device): array => collect(['light', 'dark'])
|
|
->crossJoin($names, $device === 'desktop' ? [1600, 800] : [1080, 540])
|
|
->map(fn (array $shot): string => "{$device}/{$shot[0]}/{$shot[1]}-{$shot[2]}.webp")
|
|
->all())
|
|
->sort()->values()->all();
|
|
|
|
$published = collect(glob(base_path('website/img/screenshots/*/*/*.webp')))
|
|
->map(fn (string $path): string => str($path)->after('website/img/screenshots/')->toString())
|
|
->sort()->values()->all();
|
|
|
|
expect($published)->toBe($expected);
|
|
});
|
|
|
|
test('the pages load nothing from another host but Plausible', function () {
|
|
foreach (websitePages() as $page => $html) {
|
|
preg_match_all('/<(?:script|img|source|link)\b[^>]*\s(?:src|srcset|href)="(https?:[^"]*)"/', $html, $matches);
|
|
|
|
expect(collect($matches[1])->reject(fn (string $url): bool => str_starts_with($url, 'https://plausible.io/'))->all())
|
|
->toBe([], "{$page} loads from another host");
|
|
}
|
|
|
|
foreach (glob(base_path('website/css/*.css')) as $stylesheet) {
|
|
expect(file_get_contents($stylesheet))->not->toMatch('/(url\(\s*[\'"]?https?:|@import)/');
|
|
}
|
|
});
|
|
|
|
test('the pages are titled, described and in English', function () {
|
|
foreach (websitePages() as $page => $html) {
|
|
expect($html)
|
|
->toContain('<html lang="en"')
|
|
->toMatch('/<title>[^<]+<\/title>/')
|
|
->toMatch('/<meta name="description" content="[^"]+"/')
|
|
->toContain('<script async src="https://plausible.io/js/pa-zWB4R2-rrBeLfwms3uIBT.js"></script>')
|
|
->toContain('plausible.init()');
|
|
}
|
|
});
|
|
|
|
test('neither the site nor the README calls the encryption end-to-end', function () {
|
|
$index = websitePages()['index.html'];
|
|
|
|
preg_match('/<section[^>]*id="features".*?<\/section>/s', $index, $features);
|
|
preg_match_all('/data-compare="sealshare-e2e">([^<]*)</', $index, $cells);
|
|
|
|
expect($features[0])->not->toMatch('/end-to-end/i')
|
|
->and($cells[1])->not->toBeEmpty()->each->toBe('No')
|
|
->and(file_get_contents(base_path('README.md')))->not->toContain('End-to-End Encryption');
|
|
});
|
|
|
|
test('nothing is left to fill in', function () {
|
|
foreach ([...websitePages(), 'theme.css' => file_get_contents(base_path('website/css/theme.css'))] as $file => $contents) {
|
|
expect(str_contains($contents, 'TODO'))->toBeFalse("{$file} still has a TODO");
|
|
}
|
|
});
|