vole
Clone
Read only, no credentials:
https://rodent.software/git/vole.git
Write over SSH with a registered key:
git clone ssh://git@0.0.0.0:2222/vole.git
A free and open-source forge that lives in your Git repo.
|
306f36fc
Keenan Nemetz
12d ago
|
1 | package web |
| 2 | ||
| 3 | import ( | |
|
1b8675b1
Keenan Nemetz
8d ago
|
4 | "encoding/json" |
|
306f36fc
Keenan Nemetz
12d ago
|
5 | "os" |
|
1b8675b1
Keenan Nemetz
8d ago
|
6 | "path/filepath" |
|
306f36fc
Keenan Nemetz
12d ago
|
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 | ||
|
ac00929f
Keenan Nemetz
12d ago
|
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 | ||
|
1b8675b1
Keenan Nemetz
8d ago
|
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. | |
|
306f36fc
Keenan Nemetz
12d ago
|
112 | func TestPrismLanguagesAreVendored(t *testing.T) { |
| 113 | t.Parallel() | |
|
1b8675b1
Keenan Nemetz
8d ago
|
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) | |
|
306f36fc
Keenan Nemetz
12d ago
|
131 | } |
| 132 | } | |
|
1b8675b1
Keenan Nemetz
8d ago
|
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) | |
|
306f36fc
Keenan Nemetz
12d ago
|
152 | } |
| 153 | } | |
| 154 | } | |
|
1b8675b1
Keenan Nemetz
8d ago
|
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()) | |
|
306f36fc
Keenan Nemetz
12d ago
|
167 | } |
| 168 | } | |
| 169 | } |