*/ 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 */ 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('toMatch('/[^<]+<\/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"); } });