vole
Clone
Read only, no credentials:
https://rodent.software/vole.git
Write over SSH with a registered key:
git clone ssh://git@rodent.software:2222/vole.git
A free and open-source forge that lives in your Git repo.
| 1 | package web |
| 2 | |
| 3 | import ( |
| 4 | "encoding/json" |
| 5 | "os" |
| 6 | "path/filepath" |
| 7 | "strings" |
| 8 | "testing" |
| 9 | ) |
| 10 | |
| 11 | func TestPrismLanguageNamesKnownFiles(t *testing.T) { |
| 12 | t.Parallel() |
| 13 | for name, want := range map[string]string{ |
| 14 | "main.go": "go", |
| 15 | "internal/run.go": "go", |
| 16 | "web/app.tsx": "typescript", |
| 17 | "script.mjs": "javascript", |
| 18 | "style.scss": "scss", |
| 19 | "deploy/nginx.conf": "nginx", |
| 20 | "app.conf": "ini", |
| 21 | "Makefile": "makefile", |
| 22 | "cmake/CMakeLists.txt": "cmake", |
| 23 | "Dockerfile": "docker", |
| 24 | "Containerfile": "docker", |
| 25 | ".editorconfig": "editorconfig", |
| 26 | "README.MD": "markdown", |
| 27 | "terraform/main.tf": "hcl", |
| 28 | // A file the highlighter does not claim stays plain: a wrong grammar is |
| 29 | // worse than no grammar. |
| 30 | "go.sum": "go-module", |
| 31 | "app.py": "python", |
| 32 | "lib.rs": "rust", |
| 33 | "main.js": "javascript", |
| 34 | "component.tsx": "typescript", |
| 35 | "deploy.tf": "hcl", |
| 36 | "schema.graphql": "graphql", |
| 37 | "queries.sql": "sql", |
| 38 | "notes.rst": "rest", |
| 39 | "LICENSE": "", |
| 40 | "notes.txt": "", |
| 41 | "image.webp": "", |
| 42 | } { |
| 43 | if got := prismLanguage(name); got != want { |
| 44 | t.Errorf("prismLanguage(%q) = %q, want %q", name, got, want) |
| 45 | } |
| 46 | } |
| 47 | } |
| 48 | |
| 49 | // The worker installs its own message handler and posts structured objects. Prism |
| 50 | // installs one too when it finds a WorkerGlobalScope, and that one expects a JSON |
| 51 | // string, so leaving it on throws JSON.parse on every job before the worker's |
| 52 | // handler runs. |
| 53 | func TestPrismWorkerDisablesPrismOwnMessageHandler(t *testing.T) { |
| 54 | t.Parallel() |
| 55 | source, err := os.ReadFile("static/prism-worker.js") |
| 56 | if err != nil { |
| 57 | t.Fatal(err) |
| 58 | } |
| 59 | if !strings.Contains(string(source), "disableWorkerMessageHandler: true") { |
| 60 | t.Fatal("prism-worker.js lets Prism parse the worker's structured messages as JSON") |
| 61 | } |
| 62 | } |
| 63 | |
| 64 | // A worker that goes back to importing every grammar up front still colours |
| 65 | // correctly, so nothing else fails: the page just pays 565 KB and a few hundred |
| 66 | // milliseconds again. This is the assertion that notices. |
| 67 | func TestPrismWorkerLoadsGrammarsOnDemand(t *testing.T) { |
| 68 | t.Parallel() |
| 69 | source, err := os.ReadFile("static/prism-worker.js") |
| 70 | if err != nil { |
| 71 | t.Fatal(err) |
| 72 | } |
| 73 | for _, want := range []string{ |
| 74 | "importScripts('prism.min.js', 'prism-manifest.js')", |
| 75 | "'prism-languages/' + name + '.js'", |
| 76 | } { |
| 77 | if !strings.Contains(string(source), want) { |
| 78 | t.Errorf("prism-worker.js no longer contains %q", want) |
| 79 | } |
| 80 | } |
| 81 | } |
| 82 | |
| 83 | // readPrismManifest parses static/prism-manifest.js. The generator writes one |
| 84 | // assignment whose value is a JSON object, so decoding stops at the end of that |
| 85 | // object and the trailing semicolon is never read. |
| 86 | func readPrismManifest(t *testing.T) map[string][]string { |
| 87 | t.Helper() |
| 88 | source, err := os.ReadFile("static/prism-manifest.js") |
| 89 | if err != nil { |
| 90 | t.Fatalf("the Prism manifest is missing: %v", err) |
| 91 | } |
| 92 | const assignment = "self.PRISM_LANGUAGES = " |
| 93 | at := strings.Index(string(source), assignment) |
| 94 | if at < 0 { |
| 95 | t.Fatal("static/prism-manifest.js does not assign self.PRISM_LANGUAGES") |
| 96 | } |
| 97 | var languages map[string][]string |
| 98 | rest := strings.NewReader(string(source[at+len(assignment):])) |
| 99 | if err := json.NewDecoder(rest).Decode(&languages); err != nil { |
| 100 | t.Fatalf("static/prism-manifest.js is not a JSON object: %v", err) |
| 101 | } |
| 102 | return languages |
| 103 | } |
| 104 | |
| 105 | // TestPrismLanguagesAreVendored holds the server's table to what the worker can |
| 106 | // load. A name here that neither the core nor the manifest defines colours |
| 107 | // nothing, and nothing fails at build time; the blob just quietly stays plain. |
| 108 | // |
| 109 | // The core covers the languages every Prism page starts with — markup, css, |
| 110 | // clike, javascript — and their aliases, so those have no files to import unless |
| 111 | // a component modifies them. |
| 112 | func TestPrismLanguagesAreVendored(t *testing.T) { |
| 113 | t.Parallel() |
| 114 | core, err := os.ReadFile("static/prism.min.js") |
| 115 | if err != nil { |
| 116 | t.Fatalf("vendored Prism is missing: %v", err) |
| 117 | } |
| 118 | inCore := func(lang string) bool { |
| 119 | // Prism registers a language as `languages.name=` or, for a name with |
| 120 | // punctuation in it, `languages["name"]=`. |
| 121 | return strings.Contains(string(core), "languages."+lang+"=") || |
| 122 | strings.Contains(string(core), `languages["`+lang+`"]=`) |
| 123 | } |
| 124 | languages := readPrismManifest(t) |
| 125 | for name, lang := range prismLanguages { |
| 126 | if inCore(lang) { |
| 127 | continue |
| 128 | } |
| 129 | if _, ok := languages[lang]; !ok { |
| 130 | t.Errorf("%q is mapped to %q, which neither static/prism.min.js nor static/prism-manifest.js defines", name, lang) |
| 131 | } |
| 132 | } |
| 133 | } |
| 134 | |
| 135 | // TestPrismGrammarsMatchTheManifest keeps the manifest and the vendored files in |
| 136 | // step in both directions: a file it names but nobody wrote only fails in a |
| 137 | // browser, as an unhighlighted blob, and a file nobody names is left behind when |
| 138 | // Prism drops a language. scripts/vendor-prism.py --check says the same thing |
| 139 | // after a network round trip; this is the answer without one. |
| 140 | func TestPrismGrammarsMatchTheManifest(t *testing.T) { |
| 141 | t.Parallel() |
| 142 | languages := readPrismManifest(t) |
| 143 | named := map[string]bool{} |
| 144 | for lang, files := range languages { |
| 145 | if len(files) == 0 { |
| 146 | t.Errorf("%q is in the manifest with no files to import", lang) |
| 147 | } |
| 148 | for _, file := range files { |
| 149 | named[file] = true |
| 150 | if _, err := os.Stat(filepath.Join("static", "prism-languages", file+".js")); err != nil { |
| 151 | t.Errorf("%q imports %s, which is not vendored: %v", lang, file, err) |
| 152 | } |
| 153 | } |
| 154 | } |
| 155 | entries, err := os.ReadDir(filepath.Join("static", "prism-languages")) |
| 156 | if err != nil { |
| 157 | t.Fatalf("the Prism grammars are missing: %v", err) |
| 158 | } |
| 159 | for _, entry := range entries { |
| 160 | name, ok := strings.CutSuffix(entry.Name(), ".js") |
| 161 | if !ok || entry.IsDir() { |
| 162 | t.Errorf("static/prism-languages/%s is not a vendored grammar", entry.Name()) |
| 163 | continue |
| 164 | } |
| 165 | if !named[name] { |
| 166 | t.Errorf("static/prism-languages/%s is in no manifest entry", entry.Name()) |
| 167 | } |
| 168 | } |
| 169 | } |
| 170 |