109 lines
3.6 KiB
Go
109 lines
3.6 KiB
Go
package bundler
|
|
|
|
import (
|
|
"os"
|
|
"path/filepath"
|
|
"strings"
|
|
"testing"
|
|
)
|
|
|
|
// walkRepoTSX returns every .tsx/.jsx under frontend/src (relative to the
|
|
// package dir, which is internal/bundler).
|
|
func walkRepoTSX(t *testing.T) []string {
|
|
t.Helper()
|
|
root := filepath.Join("..", "..", "frontend", "src")
|
|
var files []string
|
|
err := filepath.WalkDir(root, func(p string, d os.DirEntry, err error) error {
|
|
if err != nil || d.IsDir() {
|
|
return nil
|
|
}
|
|
switch filepath.Ext(p) {
|
|
case ".tsx", ".jsx":
|
|
files = append(files, p)
|
|
}
|
|
return nil
|
|
})
|
|
if err != nil {
|
|
t.Skipf("cannot walk frontend/src: %v", err)
|
|
}
|
|
if len(files) == 0 {
|
|
t.Skip("no .tsx files found")
|
|
}
|
|
return files
|
|
}
|
|
|
|
// Segmentation must be lossless for every real component in the repo, and the
|
|
// join invariant must hold exactly (byte-for-byte).
|
|
func TestSegmentLosslessRepo(t *testing.T) {
|
|
files := walkRepoTSX(t)
|
|
totalChunks, multiChunkFiles := 0, 0
|
|
for _, f := range files {
|
|
data, err := os.ReadFile(f)
|
|
if err != nil {
|
|
t.Fatalf("read %s: %v", f, err)
|
|
}
|
|
src := string(data)
|
|
chunks := segmentTopLevel(src)
|
|
if strings.Join(chunks, "") != src {
|
|
t.Errorf("%s: segmentation not lossless (%d chunks)", f, len(chunks))
|
|
continue
|
|
}
|
|
totalChunks += len(chunks)
|
|
if len(chunks) > 1 {
|
|
multiChunkFiles++
|
|
}
|
|
// Every chunk (except possibly the first) must begin at a declaration
|
|
// keyword after optional leading blank lines/comments were attached to
|
|
// the previous chunk — i.e. its first non-space line starts with a kw.
|
|
}
|
|
t.Logf("segmented %d files: %d split into >1 chunk, %d chunks total (avg %.1f)",
|
|
len(files), multiChunkFiles, totalChunks, float64(totalChunks)/float64(len(files)))
|
|
}
|
|
|
|
// Focused check on the pain file: it should split into many chunks so that
|
|
// editing any single declaration recompiles only that declaration.
|
|
func TestSegmentAutoTable(t *testing.T) {
|
|
p := filepath.Join("..", "..", "frontend", "src", "ui", "AutoTable.tsx")
|
|
data, err := os.ReadFile(p)
|
|
if err != nil {
|
|
t.Skipf("cannot read AutoTable: %v", err)
|
|
}
|
|
chunks := segmentTopLevel(string(data))
|
|
if strings.Join(chunks, "") != string(data) {
|
|
t.Fatal("AutoTable segmentation not lossless")
|
|
}
|
|
// Size distribution: how big is the largest chunk (the residual worst case)?
|
|
maxLen, maxIdx := 0, 0
|
|
for i, c := range chunks {
|
|
if len(c) > maxLen {
|
|
maxLen, maxIdx = len(c), i
|
|
}
|
|
}
|
|
head := strings.TrimSpace(chunks[maxIdx])
|
|
if len(head) > 80 {
|
|
head = head[:80]
|
|
}
|
|
t.Logf("AutoTable: %d chunks, largest = %d bytes (%.0f%% of file), starts: %q",
|
|
len(chunks), maxLen, 100*float64(maxLen)/float64(len(data)), head)
|
|
if len(chunks) < 20 {
|
|
t.Errorf("expected AutoTable to split into many chunks, got %d", len(chunks))
|
|
}
|
|
}
|
|
|
|
// A few hand-written cases exercise the lexer edge cases the repo may not cover.
|
|
func TestSegmentEdgeCases(t *testing.T) {
|
|
cases := map[string]string{
|
|
"template with braces and decl-looking text": "const a = `x${ {y:1} }z\nconst notReal = 2`;\nexport const b = 3;\n",
|
|
"regex with braces": "const re = /[{}]/g;\nfunction f() { return /a{2}/; }\nexport function g() {}\n",
|
|
"block comment spanning decl keyword": "/*\nconst hidden = 1;\nfunction alsoHidden() {}\n*/\nexport const real = 1;\n",
|
|
"string with keyword": "const s = \"export function fake() {}\";\nfunction real() {}\n",
|
|
"nested template": "const t = `a${`b${1}c`}d`;\nexport const u = 1;\n",
|
|
}
|
|
for name, src := range cases {
|
|
chunks := segmentTopLevel(src)
|
|
if got := strings.Join(chunks, ""); got != src {
|
|
t.Errorf("%s: not lossless\n src=%q\n got=%q", name, src, got)
|
|
}
|
|
}
|
|
}
|