aboutsummaryrefslogtreecommitdiffhomepage
path: root/src/client/pwa.test.ts
blob: b19eb795aa4b6f9715c71838574abaad48884efa (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
/**
 * @vitest-environment node
 */
import { readFileSync } from "node:fs";
import { resolve } from "node:path";
import { describe, expect, it } from "vitest";

const projectRoot = resolve(__dirname, "../..");

describe("PWA Configuration", () => {
	describe("Web Manifest (via vite.config.ts)", () => {
		it("has required manifest fields in vite config", () => {
			const viteConfig = readFileSync(
				resolve(projectRoot, "vite.config.ts"),
				"utf-8",
			);

			// Verify manifest configuration exists
			expect(viteConfig).toContain('name: "Kioku"');
			expect(viteConfig).toContain('short_name: "Kioku"');
			expect(viteConfig).toContain(
				'description: "A spaced repetition learning app"',
			);
			expect(viteConfig).toContain('theme_color: "#1a535c"');
			expect(viteConfig).toContain('background_color: "#faf9f6"');
			expect(viteConfig).toContain('display: "standalone"');
			expect(viteConfig).toContain('start_url: "/"');
		});

		it("has icon configuration", () => {
			const viteConfig = readFileSync(
				resolve(projectRoot, "vite.config.ts"),
				"utf-8",
			);

			expect(viteConfig).toContain('src: "icon.svg"');
			expect(viteConfig).toContain('type: "image/svg+xml"');
			expect(viteConfig).toContain('purpose: "any maskable"');
		});

		it("has registerType autoUpdate", () => {
			const viteConfig = readFileSync(
				resolve(projectRoot, "vite.config.ts"),
				"utf-8",
			);

			expect(viteConfig).toContain('registerType: "autoUpdate"');
		});
	});

	describe("Workbox Configuration", () => {
		it("has workbox caching patterns configured", () => {
			const viteConfig = readFileSync(
				resolve(projectRoot, "vite.config.ts"),
				"utf-8",
			);

			expect(viteConfig).toContain("globPatterns:");
			expect(viteConfig).toContain("runtimeCaching:");
		});

		it("has navigate fallback for offline support", () => {
			const viteConfig = readFileSync(
				resolve(projectRoot, "vite.config.ts"),
				"utf-8",
			);

			expect(viteConfig).toContain('navigateFallback: "/offline.html"');
		});

		it("excludes API routes from navigate fallback", () => {
			const viteConfig = readFileSync(
				resolve(projectRoot, "vite.config.ts"),
				"utf-8",
			);

			expect(viteConfig).toContain("navigateFallbackDenylist:");
			expect(viteConfig).toContain("/^\\/api\\//");
		});

		it("has image caching configuration", () => {
			const viteConfig = readFileSync(
				resolve(projectRoot, "vite.config.ts"),
				"utf-8",
			);

			expect(viteConfig).toContain('handler: "CacheFirst"');
			expect(viteConfig).toContain('cacheName: "images-cache"');
		});
	});

	describe("Offline Fallback Page", () => {
		const offlineHtml = readFileSync(
			resolve(projectRoot, "public/offline.html"),
			"utf-8",
		);

		it("exists and is valid HTML", () => {
			expect(offlineHtml).toContain("<!doctype html>");
			expect(offlineHtml).toContain("<html");
			expect(offlineHtml).toContain("</html>");
		});

		it("has proper meta tags", () => {
			expect(offlineHtml).toContain('charset="UTF-8"');
			expect(offlineHtml).toContain('name="viewport"');
			expect(offlineHtml).toContain('name="theme-color"');
			expect(offlineHtml).toContain('content="#4CAF50"');
		});

		it("has appropriate title", () => {
			expect(offlineHtml).toContain("<title>Kioku - Offline</title>");
		});

		it("displays offline message", () => {
			expect(offlineHtml).toContain("You're Offline");
			expect(offlineHtml).toContain("lost your internet connection");
		});

		it("has retry button", () => {
			expect(offlineHtml).toContain("<button");
			expect(offlineHtml).toContain("Try Again");
			expect(offlineHtml).toContain("window.location.reload()");
		});

		it("has Kioku icon", () => {
			expect(offlineHtml).toContain("<svg");
			expect(offlineHtml).toContain('fill="#4CAF50"');
		});
	});

	describe("Icon", () => {
		const iconSvg = readFileSync(
			resolve(projectRoot, "public/icon.svg"),
			"utf-8",
		);

		it("exists and is valid SVG", () => {
			expect(iconSvg).toContain("<svg");
			expect(iconSvg).toContain("</svg>");
			expect(iconSvg).toContain('xmlns="http://www.w3.org/2000/svg"');
		});

		it("has proper viewBox for square icon", () => {
			expect(iconSvg).toContain('viewBox="0 0 512 512"');
		});

		it("uses theme color", () => {
			expect(iconSvg).toContain('fill="#4CAF50"');
		});

		it("contains K letter for Kioku branding", () => {
			expect(iconSvg).toContain(">K<");
		});
	});
});