|
1 | | -import test from 'node:test' |
| 1 | +import test, { describe } from 'node:test' |
2 | 2 | import assert from 'node:assert/strict' |
3 | | -import { readFileSync } from 'node:fs' |
| 3 | +import { readFileSync, existsSync } from 'node:fs' |
| 4 | +import { execSync } from 'node:child_process' |
4 | 5 | import { fileURLToPath } from 'node:url' |
5 | 6 | import path from 'node:path' |
6 | 7 |
|
@@ -112,3 +113,78 @@ test('package.json hat test-Script (Bug #3 Fix)', () => { |
112 | 113 | assert.ok(pkg.scripts.test.includes('library.test.mjs'), 'test-Script muss library.test.mjs einschließen') |
113 | 114 | assert.ok(pkg.scripts.test.includes('pwa.test.mjs'), 'test-Script muss pwa.test.mjs einschließen') |
114 | 115 | }) |
| 116 | + |
| 117 | +// ────────────────────────────────────────────────────────────── |
| 118 | +// iOS PWA-Härtung |
| 119 | +// ────────────────────────────────────────────────────────────── |
| 120 | + |
| 121 | +describe('index.html iOS-PWA-Meta', () => { |
| 122 | + const html = readFileSync(path.join(root, 'index.html'), 'utf8') |
| 123 | + |
| 124 | + test('viewport-Meta enthält viewport-fit=cover', () => { |
| 125 | + assert.match(html, /<meta[^>]*name="viewport"[^>]*viewport-fit=cover/) |
| 126 | + }) |
| 127 | + |
| 128 | + test('viewport-Meta enthält width=device-width und initial-scale=1', () => { |
| 129 | + assert.match(html, /<meta[^>]*name="viewport"[^>]*width=device-width/) |
| 130 | + assert.match(html, /<meta[^>]*name="viewport"[^>]*initial-scale=1/) |
| 131 | + }) |
| 132 | + |
| 133 | + test('apple-mobile-web-app-title ist gesetzt', () => { |
| 134 | + assert.match(html, /<meta[^>]*name="apple-mobile-web-app-title"[^>]*content="[^"]+"/) |
| 135 | + }) |
| 136 | + |
| 137 | + test('apple-mobile-web-app-status-bar-style ist gesetzt', () => { |
| 138 | + assert.match(html, /<meta[^>]*name="apple-mobile-web-app-status-bar-style"[^>]*content="[^"]+"/) |
| 139 | + }) |
| 140 | + |
| 141 | + test('apple-touch-icon hat sizes="180x180"', () => { |
| 142 | + assert.match(html, /<link[^>]*rel="apple-touch-icon"[^>]*sizes="180x180"/) |
| 143 | + }) |
| 144 | + |
| 145 | + test('apple-touch-icon verweist auf apple-touch-icon-180.png', () => { |
| 146 | + assert.match(html, /<link[^>]*rel="apple-touch-icon"[^>]*href="[^"]*apple-touch-icon-180\.png"/) |
| 147 | + }) |
| 148 | + |
| 149 | + test('KEIN apple-mobile-web-app-capable (deprecated seit iOS 11.3)', () => { |
| 150 | + assert.doesNotMatch(html, /apple-mobile-web-app-capable/, 'deprecated — darf nicht gesetzt sein') |
| 151 | + }) |
| 152 | + |
| 153 | + test('keine doppelten viewport-Meta-Tags', () => { |
| 154 | + const matches = html.match(/<meta[^>]*name="viewport"/g) ?? [] |
| 155 | + assert.equal(matches.length, 1, `Genau 1 viewport-Meta erwartet, gefunden: ${matches.length}`) |
| 156 | + }) |
| 157 | + |
| 158 | + test('theme-color Meta-Tag ist gesetzt', () => { |
| 159 | + assert.match(html, /<meta[^>]*name="theme-color"[^>]*content="[^"]+"/) |
| 160 | + }) |
| 161 | +}) |
| 162 | + |
| 163 | +describe('apple-touch-icon-180.png — opaques RGB', () => { |
| 164 | + const iconPath = path.join(root, 'icons', 'apple-touch-icon-180.png') |
| 165 | + |
| 166 | + test('apple-touch-icon-180.png existiert', () => { |
| 167 | + assert.ok(existsSync(iconPath), 'icons/apple-touch-icon-180.png fehlt') |
| 168 | + }) |
| 169 | + |
| 170 | + test('apple-touch-icon-180.png ist opakes RGB (keine Transparenz)', () => { |
| 171 | + const p = iconPath.replace(/\\/g, '/') |
| 172 | + const result = execSync( |
| 173 | + `python -c "from PIL import Image; img=Image.open('${p}'); d=list(img.getdata()); t=sum(1 for px in d if len(px)==4 and px[3]==0); print(t)"`, |
| 174 | + { encoding: 'utf8' } |
| 175 | + ).trim() |
| 176 | + assert.equal(result, '0', `apple-touch-icon-180.png hat transparente Pixel: ${result}`) |
| 177 | + }) |
| 178 | +}) |
| 179 | + |
| 180 | +describe('sw.js iOS-Härtung', () => { |
| 181 | + const sw = readFileSync(path.join(root, 'sw.js'), 'utf8') |
| 182 | + |
| 183 | + test('CACHE_NAME ist v2 oder höher (nach iOS-Härtung)', () => { |
| 184 | + assert.match(sw, /softwarecenter-companion-v[2-9]/, 'CACHE_NAME muss v2+ sein') |
| 185 | + }) |
| 186 | + |
| 187 | + test('apple-touch-icon-180.png ist in OFFLINE_ASSETS gecacht', () => { |
| 188 | + assert.ok(sw.includes('apple-touch-icon-180.png'), 'apple-touch-icon-180.png fehlt in OFFLINE_ASSETS') |
| 189 | + }) |
| 190 | +}) |
0 commit comments