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) } } }