| 1 | package gallery |
| 2 | |
| 3 | import ( |
| 4 | "strings" |
| 5 | "testing" |
| 6 | |
| 7 | "amadan.net/rastrillo/fiesta" |
| 8 | ) |
| 9 | |
| 10 | // The integration gate: what holds only with the four streams in |
| 11 | // together, the shell's rail over Getting started's rows, the URL gate |
| 12 | // over the demo, the modal and the shells in the rail, and the widget |
| 13 | // count with the Overview's demo in it. Each is a preservation of a |
| 14 | // stream's own gate, extended to what another stream added, and each |
| 15 | // was already passing when written; the comment on each names the |
| 16 | // mutation that turns it red. |
| 17 | |
| 18 | // TestTheSidebarLinksGettingStartedExactlyOnce extends |
| 19 | // TestTheSidebarLinksEverythingOnThePageExactlyOnce to Getting started, |
| 20 | // whose rows are another stream's: in every theme and locale the rail's |
| 21 | // fragments into getting-started.html are the page's anchored rows, in |
| 22 | // order, one per file the release ships and the page lists. Mutation: |
| 23 | // assetNav dropping its first item reports the rail one row short on |
| 24 | // every Getting started page. |
| 25 | func TestTheSidebarLinksGettingStartedExactlyOnce(t *testing.T) { |
| 26 | files := render(t, "") |
| 27 | s := newTestSite(t, fiesta.FS(), "") |
| 28 | file := s.fileOf("getting-started") |
| 29 | read := 0 |
| 30 | for _, dir := range dirs() { |
| 31 | theme, locale, _ := strings.Cut(strings.TrimSuffix(dir, "/"), "/") |
| 32 | name := dir + file |
| 33 | page := kindPage(t, files, theme, locale, file) |
| 34 | rail := railOf(t, name, page) |
| 35 | self := s.href(theme, locale, file) |
| 36 | var anchors, fragments []string |
| 37 | for _, m := range anchorMarker.FindAllStringSubmatch(page, -1) { |
| 38 | anchors = append(anchors, m[1]) |
| 39 | } |
| 40 | for _, m := range navHref.FindAllStringSubmatch(rail, -1) { |
| 41 | if strings.HasPrefix(m[1], self+"#") { |
| 42 | fragments = append(fragments, strings.TrimPrefix(m[1], self+"#")) |
| 43 | } |
| 44 | } |
| 45 | view, err := s.buildAssets(s.newPage(theme, locale, "getting-started", file, "")) |
| 46 | if err != nil { |
| 47 | t.Fatal(err) |
| 48 | } |
| 49 | if len(view.List) == 0 { |
| 50 | t.Fatalf("%s lists no file", name) |
| 51 | } |
| 52 | if len(fragments) != len(view.List) || strings.Join(fragments, " ") != strings.Join(anchors, " ") { |
| 53 | t.Errorf("%s: the rail links %d rows %v, the page anchors %v and lists %d files", name, len(fragments), fragments, anchors, len(view.List)) |
| 54 | } |
| 55 | read++ |
| 56 | } |
| 57 | if read != len(dirs()) { |
| 58 | t.Fatalf("read %d Getting started pages, want %d", read, len(dirs())) |
| 59 | } |
| 60 | } |
| 61 | |
| 62 | // TestEveryDemoInTheRailResolves extends TestEveryGalleryURLResolves |
| 63 | // to the Demos the rail and the phone index carry, which three streams |
| 64 | // wrote: at a mount, on every page of every directory, the rail's Demos |
| 65 | // are the five shells, the modal route and the demo application, each |
| 66 | // a file the render wrote under that directory, and the Overview's |
| 67 | // phone index lists the same seven; and every link the demo's |
| 68 | // documents and the modal route mark as the gallery's resolves too. |
| 69 | // Mutation: demoHref writing "demo.htm" reports the demo application |
| 70 | // as a file the render did not write, here and in the URL gate. |
| 71 | func TestEveryDemoInTheRailResolves(t *testing.T) { |
| 72 | const mount = "/x" |
| 73 | files := render(t, mount) |
| 74 | s := newTestSite(t, fiesta.FS(), mount) |
| 75 | checked := 0 |
| 76 | for _, dir := range dirs() { |
| 77 | theme, locale, _ := strings.Cut(strings.TrimSuffix(dir, "/"), "/") |
| 78 | want := demoHrefs(t, s, theme, locale) |
| 79 | for _, pk := range s.pageKinds() { |
| 80 | name := dir + pk.File |
| 81 | page := kindPage(t, files, theme, locale, pk.File) |
| 82 | groups := railSections(railOf(t, name, page)) |
| 83 | if len(groups) == 0 { |
| 84 | t.Fatalf("%s: no rail", name) |
| 85 | } |
| 86 | demos := groups[len(groups)-1] |
| 87 | got := map[string]bool{} |
| 88 | for _, m := range navHref.FindAllStringSubmatch(demos.Body, -1) { |
| 89 | got[m[1]] = true |
| 90 | path := strings.TrimPrefix(m[1], mount+"/") |
| 91 | if _, ok := files[path]; !ok || !strings.HasPrefix(path, dir) { |
| 92 | t.Errorf("%s: the Demos link %q, which is not a file of %s", name, m[1], dir) |
| 93 | } |
| 94 | checked++ |
| 95 | } |
| 96 | if !sameKeys(got, want) { |
| 97 | t.Errorf("%s: the Demos are %v, want %v", name, keysOf(got), keysOf(want)) |
| 98 | } |
| 99 | if pk.Kind == "overview" { |
| 100 | m := indexNav.FindStringSubmatch(page) |
| 101 | if m == nil { |
| 102 | t.Fatalf("%s: no phone index", name) |
| 103 | } |
| 104 | rows := map[string]bool{} |
| 105 | for _, l := range navHref.FindAllStringSubmatch(m[2], -1) { |
| 106 | if want[l[1]] { |
| 107 | rows[l[1]] = true |
| 108 | } |
| 109 | } |
| 110 | if !sameKeys(rows, want) { |
| 111 | t.Errorf("%s: the phone index lists the Demos %v, want %v", name, keysOf(rows), keysOf(want)) |
| 112 | } |
| 113 | } |
| 114 | } |
| 115 | for _, extra := range s.extraDocumentNames() { |
| 116 | for _, u := range galleryURLs(t, files[dir+extra]) { |
| 117 | path, _, _ := strings.Cut(strings.TrimPrefix(u, mount+"/"), "#") |
| 118 | if _, ok := files[path]; !ok || !strings.HasPrefix(u, mount+"/") { |
| 119 | t.Errorf("%s%s links %q, which the render did not write", dir, extra, u) |
| 120 | } |
| 121 | checked++ |
| 122 | } |
| 123 | } |
| 124 | } |
| 125 | if checked == 0 { |
| 126 | t.Fatal("no link was checked") |
| 127 | } |
| 128 | } |
| 129 | |
| 130 | // TestEveryWidgetIsFramedAsItsKindSays extends |
| 131 | // TestEveryExampleIsFramedDesktopMobileAndHTML to the Overview's demo, |
| 132 | // another stream's widget in the first stream's markup: on day/en the |
| 133 | // example pages hold one widget per state or per grouped example, each |
| 134 | // with Desktop, Mobile and HTML and its panel, and in every directory |
| 135 | // the Overview holds exactly one more, the demo, at the page width with |
| 136 | // Desktop and Mobile and no HTML; every widget's radios carry a name of |
| 137 | // their own on the page. Mutation: the demo's widget given a Source |
| 138 | // (an HTML panel) reports the Overview's widget as an example's. |
| 139 | func TestEveryWidgetIsFramedAsItsKindSays(t *testing.T) { |
| 140 | files := render(t, "") |
| 141 | s := newTestSite(t, fiesta.FS(), "") |
| 142 | theme := fiesta.ThemeNames()[0] |
| 143 | examples, overviews := 0, 0 |
| 144 | for _, e := range s.m.Examples { |
| 145 | for _, w := range widgetsOf(examplePage(t, files, theme, "en", e.Name)) { |
| 146 | examples++ |
| 147 | var order string |
| 148 | for _, m := range tabClass.FindAllStringSubmatch(w, -1) { |
| 149 | order += m[1] |
| 150 | } |
| 151 | if order != "dmh" || panelOf(w) == "" { |
| 152 | t.Errorf("%s: a widget with tabs %q and panel %v, want Desktop, Mobile, HTML and its panel", e.Name, order, panelOf(w) != "") |
| 153 | } |
| 154 | } |
| 155 | } |
| 156 | for _, dir := range dirs() { |
| 157 | name := dir + "index.html" |
| 158 | page := string(files[name]) |
| 159 | widgets := widgetsOf(page) |
| 160 | if len(widgets) != 1 { |
| 161 | t.Errorf("%s: %d widgets, want the demo alone", name, len(widgets)) |
| 162 | continue |
| 163 | } |
| 164 | overviews++ |
| 165 | w := widgets[0] |
| 166 | var order string |
| 167 | for _, m := range tabClass.FindAllStringSubmatch(w, -1) { |
| 168 | order += m[1] |
| 169 | } |
| 170 | if order != "dm" || panelOf(w) != "" || !strings.Contains(page, `<div class="ds-view ds-view--page" style="`) { |
| 171 | t.Errorf("%s: the demo's widget has tabs %q and panel %v, want a page-width widget with Desktop and Mobile and no HTML", name, order, panelOf(w) != "") |
| 172 | } |
| 173 | names := map[string]int{} |
| 174 | for _, r := range radioInput.FindAllStringSubmatch(page, -1) { |
| 175 | names[r[1]]++ |
| 176 | } |
| 177 | if len(names) != 1 || names["demo-app-0"] != 2 { |
| 178 | t.Errorf("%s: the radios are %v, want the demo's two under its own name", name, names) |
| 179 | } |
| 180 | } |
| 181 | if examples < 100 || overviews != len(dirs()) { |
| 182 | t.Fatalf("%d example widgets and %d Overview widgets read; the walk read too little", examples, overviews) |
| 183 | } |
| 184 | t.Logf("%d example widgets on day/en and the demo on %d Overviews", examples, overviews) |
| 185 | } |
| 186 | |