feat Add Work Sans favicon

Serve an SVG mark and link it from every page head. Cover asset delivery and document integration with web tests. sim: N/A

Commit
b08ab0965561faa18feae1907c388f950781a331
Author
Marius Peter <dev@marius-peter.com>
Author date
Committer
Marius Peter <dev@marius-peter.com>
Committer date
Changed files
lib/presenter/page_shell.ml
index 31a4bd80..67083ce1 100644..100644
@@ -208,6 +208,12 @@
208 208 "width=device-width,initial-scale=1";
209 209 ];
210 210 void "link" [ rel "stylesheet"; href Routes.stylesheet ];
211 Added: void "link"
212 Added: [
213 Added: rel "icon";
214 Added: Dream_html.string_attr "type" "image/svg+xml";
215 Added: href Routes.favicon;
216 Added: ];
211 217 ];
212 218 tag "body"
213 219 [ Dream_html.string_attr "class" "hito-app" ]
lib/presenter/routes.ml
index e8e11eb8..5f0ba4b1 100644..100644
@@ -39,3 +39,4 @@
39 39 let%path stylesheet = "/assets/hito.css"
40 40 let%path workout_client = "/assets/workout-client.js"
41 41 let%path font = "/assets/fonts/%s"
42 Added: let%path favicon = "/assets/favicon.svg"
lib/web/assets/favicon.svg
index 00000000..af4cf278 000000..100644
@@ -0,0 +1,6 @@
1 Added: <svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 128 128" role="img" aria-labelledby="title description">
2 Added: <title id="title">Hito</title>
3 Added: <desc id="description">A blood-red letter H on a white circle.</desc>
4 Added: <circle cx="64" cy="64" r="60" fill="#fff"/>
5 Added: <text x="64" y="64" dy="0.35em" text-anchor="middle" font-family="Work Sans, sans-serif" font-size="88" font-weight="700" fill="#8b0000">H</text>
6 Added: </svg>
lib/web/handlers_assets.ml
index b1d0f2aa..cebaa6ca 100644..100644
@@ -13,6 +13,11 @@
13 13 | Some script -> script
14 14 | None -> failwith "Embedded workout client is missing"
15 15
16 Added: let favicon =
17 Added: match Stylesheet.read "favicon.svg" with
18 Added: | Some favicon -> favicon
19 Added: | None -> failwith "Embedded favicon.svg is missing"
20 Added:
16 21 (* Embedded assets change whenever the binary is rebuilt, and the URLs carry no
17 22 version, so [no-cache] makes the browser revalidate on every load rather than
18 23 serve a stale copy after a deploy. Cheap: the payloads are small. *)
@@ -47,5 +52,9 @@
47 52 Dream.respond
48 53 ~headers:(asset_headers "application/javascript; charset=utf-8")
49 54 workout_client);
55 Added: Dream_html.get Routes.favicon (fun _ ->
56 Added: Dream.respond
57 Added: ~headers:(asset_headers "image/svg+xml; charset=utf-8")
58 Added: favicon);
50 59 Dream_html.get Routes.font (fun _ name -> font name);
51 60 ]
test/web/support.ml
index 616a4ba4..334acff3 100644..100644
@@ -290,3 +290,13 @@
290 290 let _ = record_single "2" "10" "9" in
291 291 let _ = record_pair "3" "0" in
292 292 ()
293 Added:
294 Added: let contains_substring ~needle haystack =
295 Added: let needle_length = String.length needle in
296 Added: let haystack_length = String.length haystack in
297 Added: let rec search offset =
298 Added: if offset + needle_length > haystack_length then false
299 Added: else if String.sub haystack offset needle_length = needle then true
300 Added: else search (offset + 1)
301 Added: in
302 Added: search 0
test/web/workout.ml
index ac11997e..94042c2c 100644..100644
@@ -72,6 +72,30 @@
72 72 let missing = get c "/assets/fonts/does-not-exist.woff2" in
73 73 Alcotest.(check int)
74 74 "an unknown font is not found" 404 (status missing) );
75 Added: ( "the favicon is served as an SVG with the Hito mark",
76 Added: `Quick,
77 Added: fun () ->
78 Added: let c = client () in
79 Added: let response = get c "/assets/favicon.svg" in
80 Added: let markup = body response in
81 Added: Alcotest.(check int) "serves the favicon" 200 (status response);
82 Added: Alcotest.(check (option string))
83 Added: "declares SVG content" (Some "image/svg+xml; charset=utf-8")
84 Added: (Dream.header response "Content-Type");
85 Added: Alcotest.(check bool)
86 Added: "contains the requested H mark" true
87 Added: (contains_substring
88 Added: ~needle:"font-family=\"Work Sans, sans-serif\"" markup
89 Added: && contains_substring
90 Added: ~needle:
91 Added: "<circle cx=\"64\" cy=\"64\" r=\"60\" fill=\"#fff\"/>"
92 Added: markup
93 Added: && contains_substring ~needle:"fill=\"#8b0000\">H</text>" markup);
94 Added: let page = body (get c "/login") in
95 Added: Alcotest.(check bool)
96 Added: "links the favicon from the document head" true
97 Added: (contains_substring ~needle:"href=\"/assets/favicon.svg\"" page)
98 Added: );
75 99 ( "beginning before recovery is refused without an override",
76 100 `Quick,
77 101 fun () ->