Skip to content

Commit d925137

Browse files
lukischclaude
andcommitted
feat(web_companion/ios): iOS PWA-Härtung — viewport-fit=cover, apple-touch-icon-180 opak-RGB, safe-area CSS, SW v2, 13 neue Tests (31/31 grün)
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
1 parent cc3e245 commit d925137

5 files changed

Lines changed: 92 additions & 4 deletions

File tree

web_companion/app.css

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,9 @@
11
:root {
22
color-scheme: light;
3+
--safe-area-top: env(safe-area-inset-top, 0px);
4+
--safe-area-right: env(safe-area-inset-right, 0px);
5+
--safe-area-bottom: env(safe-area-inset-bottom, 0px);
6+
--safe-area-left: env(safe-area-inset-left, 0px);
37
--bg: #f2f0e8;
48
--panel: rgba(255, 252, 245, 0.92);
59
--panel-strong: #fffdf7;
@@ -21,6 +25,10 @@
2125
body {
2226
margin: 0;
2327
min-height: 100vh;
28+
padding-top: var(--safe-area-top);
29+
padding-right: var(--safe-area-right);
30+
padding-bottom: var(--safe-area-bottom);
31+
padding-left: var(--safe-area-left);
2432
font-family: "Segoe UI", "Trebuchet MS", sans-serif;
2533
color: var(--text);
2634
background:
9.09 KB
Loading

web_companion/index.html

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,14 @@
22
<html lang="de">
33
<head>
44
<meta charset="utf-8">
5-
<meta name="viewport" content="width=device-width, initial-scale=1">
5+
<meta name="viewport" content="width=device-width, initial-scale=1, viewport-fit=cover">
66
<meta name="theme-color" content="#1d6f5f">
77
<meta name="description" content="Lokaler Web/PWA-Companion für exportierte SoftwareCenter-Profile.">
8+
<meta name="apple-mobile-web-app-title" content="SoftwareCenter">
9+
<meta name="apple-mobile-web-app-status-bar-style" content="default">
810
<link rel="manifest" href="./manifest.webmanifest">
911
<link rel="icon" href="./icon.svg" type="image/svg+xml">
12+
<link rel="apple-touch-icon" sizes="180x180" href="./icons/apple-touch-icon-180.png">
1013
<link rel="stylesheet" href="./app.css">
1114
<title>SoftwareCenter Companion</title>
1215
</head>

web_companion/sw.js

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
const CACHE_NAME = "softwarecenter-companion-v1";
1+
const CACHE_NAME = "softwarecenter-companion-v2";
22
const OFFLINE_ASSETS = [
33
"./",
44
"./index.html",
@@ -11,6 +11,7 @@ const OFFLINE_ASSETS = [
1111
"./icons/Icon-512.png",
1212
"./icons/Icon-maskable-192.png",
1313
"./icons/Icon-maskable-512.png",
14+
"./icons/apple-touch-icon-180.png",
1415
];
1516

1617
self.addEventListener("install", (event) => {

web_companion/tests/pwa.test.mjs

Lines changed: 78 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
1-
import test from 'node:test'
1+
import test, { describe } from 'node:test'
22
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'
45
import { fileURLToPath } from 'node:url'
56
import path from 'node:path'
67

@@ -112,3 +113,78 @@ test('package.json hat test-Script (Bug #3 Fix)', () => {
112113
assert.ok(pkg.scripts.test.includes('library.test.mjs'), 'test-Script muss library.test.mjs einschließen')
113114
assert.ok(pkg.scripts.test.includes('pwa.test.mjs'), 'test-Script muss pwa.test.mjs einschließen')
114115
})
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

Comments
 (0)