rastrillo / fiesta Public

Clone
git clone https://amadan.net/rastrillo/fiesta

Plain git — no account needed to clone.

Download

Download this file

1package check
2
3import (
4 "net/url"
5 "path"
6 "regexp"
7 "strings"
8 "testing"
9)
10
11// parityLinks rewrites a document's asset links to the flat names the
12// parity rig serves beside the page: a layout's ../../css/tokens.css,
13// ../../css/themes/<theme>.css, ../../css/shell.css and ../../js/<file>
14// become tokens.css, theme.css, shell.css and <file>, and the depth
15// fixture's /tokens.css, /theme.css and /<file>.js the same. Only the
16// stylesheet links and script tags are touched: an anchor in the body
17// is the page's own, and stays.
18//
19// Its contract is the tree's own links. A path, relative or from the
20// root, is flattened to its last segment, and a path with a /themes/
21// segment is the theme, whichever theme it names. A URL with a scheme
22// or a host (https://fonts.example/x.css, //cdn.example/y.js) is left
23// as it is: the rig has nothing to serve for it, and a layout that
24// gains one wants it fetched from where it points. Two files that
25// share a base name under different directories would collide; the
26// tree has none.
27func parityLinks(doc string) string {
28 return assetLinks(doc, "")
29}
30
31// assetLinks is parityLinks with the flat names under dir: "" for the
32// names beside the page, "/" for the root paths the sizing mux serves
33// (shellPage), so a page served below the root still finds them.
34func assetLinks(doc, dir string) string {
35 return assetLink.ReplaceAllStringFunc(doc, func(tag string) string {
36 return assetURL.ReplaceAllStringFunc(tag, func(attr string) string {
37 m := assetURL.FindStringSubmatch(attr)
38 if u, err := url.Parse(m[2]); err != nil || u.Scheme != "" || u.Host != "" {
39 return attr
40 }
41 name := path.Base(m[2])
42 if strings.Contains(m[2], "/themes/") {
43 name = "theme.css"
44 }
45 return m[1] + dir + name + `"`
46 })
47 })
48}
49
50// assetURL is the href or src attribute inside one asset link.
51var assetURL = regexp.MustCompile(`((?:href|src)=")([^"]*)"`)
52
53// TestParityLinksAreRewritten holds parityLinks to one layout document:
54// every stylesheet and script it links comes out as a flat name beside
55// the page, the theme under whichever name the layout linked it as
56// theme.css, the body's own links untouched, and nothing else in the
57// document moved. A rewrite that missed a link would leave the parity
58// rig serving a 404 the harness reports, which is a failure for the
59// wrong reason; one that touched an anchor would compare a page the
60// layout never wrote.
61func TestParityLinksAreRewritten(t *testing.T) {
62 m := manifest(t)
63 state, ok := m.Default("layout-sidebar")
64 if !ok || state == "" {
65 t.Fatal("examples.json names no default state for layout-sidebar")
66 }
67 doc := defaultMarkup(t, "layout-sidebar")
68 for _, want := range []string{`href="../../css/tokens.css"`, `href="../../css/themes/day.css"`, `href="../../css/shell.css"`, `src="../../js/shell.js"`, `href="/"`} {
69 if !strings.Contains(doc, want) {
70 t.Fatalf("layout-sidebar/%s no longer links %s; the control has nothing to rewrite", state, want)
71 }
72 }
73
74 got := parityLinks(doc)
75 for _, want := range []string{
76 `<link rel="stylesheet" href="tokens.css">`,
77 `<link rel="stylesheet" href="theme.css">`,
78 `<link rel="stylesheet" href="shell.css">`,
79 `<script defer src="fiesta.js"></script>`,
80 `<script defer blocking="render" src="shell.js"></script>`,
81 } {
82 if !strings.Contains(got, want) {
83 t.Errorf("the rewritten layout does not link %s", want)
84 }
85 }
86 if strings.Contains(got, "../../") {
87 t.Errorf("a relative link survived the rewrite:\n%s", linkLines(got))
88 }
89 if n := strings.Count(got, `href="theme.css"`); n != 1 {
90 t.Errorf("the rewritten layout links theme.css %d times, want once", n)
91 }
92 if strings.Count(got, `href="/"`) != strings.Count(doc, `href="/"`) {
93 t.Errorf("the rewrite touched the body's own links: %d href=\"/\" before, %d after", strings.Count(doc, `href="/"`), strings.Count(got, `href="/"`))
94 }
95 // The link tags are the only lines that change: strip every asset
96 // link from both and what is left is byte for byte the same.
97 strip := func(s string) string { return assetLink.ReplaceAllString(s, "") }
98 if strip(got) != strip(doc) {
99 t.Errorf("the rewrite changed more than the asset links")
100 }
101 if strip(doc) == doc {
102 t.Fatal("the control stripped nothing; assetLink matches no link in the layout")
103 }
104
105 // The limits: a URL with a scheme or a host is not the rig's to
106 // serve and stays as written, beside the tree's own links, which
107 // still flatten; and the depth fixture's root paths flatten too.
108 const foreign = `<link rel="stylesheet" href="https://fonts.example/face.css"><script defer src="//cdn.example/lib.js"></script>`
109 mixed := `<head>` + foreign + `<link rel="stylesheet" href="../../css/themes/signal.css"><script defer src="/fiesta.js"></script></head>`
110 if got, want := parityLinks(mixed), `<head>`+foreign+`<link rel="stylesheet" href="theme.css"><script defer src="fiesta.js"></script></head>`; got != want {
111 t.Errorf("the rewrite of foreign and root links:\n got %s\n want %s", got, want)
112 }
113 // Under a directory, for the shell drives: the same names from the
114 // root, the foreign URLs still untouched.
115 if got, want := assetLinks(mixed, "/"), `<head>`+foreign+`<link rel="stylesheet" href="/theme.css"><script defer src="/fiesta.js"></script></head>`; got != want {
116 t.Errorf("the rewrite under /:\n got %s\n want %s", got, want)
117 }
118}
119
120// assetLink is a stylesheet link or a script tag, the tags parityLinks
121// rewrites; the test above uses it to prove nothing else changed.
122var assetLink = regexp.MustCompile(`<link[^>]*rel="stylesheet"[^>]*>|<script[^>]*src="[^"]*"[^>]*></script>`)
123
124// linkLines is the asset links of a document, one per line, for a
125// failure message.
126func linkLines(doc string) string {
127 return strings.Join(assetLink.FindAllString(doc, -1), "\n")
128}
129