diff --git a/internal/contract/enums/enums_test.go b/internal/contract/enums/enums_test.go index 0768c24..6738dbb 100644 --- a/internal/contract/enums/enums_test.go +++ b/internal/contract/enums/enums_test.go @@ -18,6 +18,7 @@ import ( "regexp" "slices" "sort" + "strings" "testing" "git.yeij.top/AskaEth/Cyrene_v2/internal/contract/enums" @@ -128,23 +129,52 @@ func TestMoodTableMatchesEmotionBuiltins(t *testing.T) { } } +// frontendTypesDir 是前端共享类型的目录:barrel(lib/types.ts)+ 按域拆分的 +// lib/types/*.ts(阶段 0 的结构拆分,方案见 DevDocs/audit/_plan.md)。 +const frontendTypesDir = "../../../frontend/web/src/lib/types" + +// readFrontendTypes 汇总 barrel 与目录下全部 .ts 的源码。按目录收集而不是写死单个文件, +// 是为了让「MODES 数组住在哪个文件」不成为本测试的耦合点——以后再搬一次也不用改它。 +// barrel 缺失(backend-only checkout)时返回 fs.ErrNotExist,由调用方跳过。 +func readFrontendTypes() ([]byte, error) { + paths := []string{frontendTypesDir + ".ts"} + entries, err := os.ReadDir(frontendTypesDir) + if err != nil && !errors.Is(err, fs.ErrNotExist) { + return nil, err + } + for _, e := range entries { + if !e.IsDir() && strings.HasSuffix(e.Name(), ".ts") { + paths = append(paths, frontendTypesDir+"/"+e.Name()) + } + } + var all []byte + for _, p := range paths { + raw, err := os.ReadFile(p) + if err != nil { + return nil, err + } + all = append(all, raw...) + all = append(all, '\n') + } + return all, nil +} + // TestModeTableMatchesFrontendModes asserts the mode table matches the frontend's -// Mode union / MODES array (frontend/web/src/lib/types.ts), the only code-level -// definition of the nine modes. This is the cross-language drift guard: adding a -// mode on either side without the other fails here. +// Mode union / MODES array, the only code-level definition of the nine modes. This +// is the cross-language drift guard: adding a mode on either side without the other +// fails here. func TestModeTableMatchesFrontendModes(t *testing.T) { t.Parallel() - const typesPath = "../../../frontend/web/src/lib/types.ts" - raw, err := os.ReadFile(typesPath) + raw, err := readFrontendTypes() if err != nil { if errors.Is(err, fs.ErrNotExist) { - t.Skipf("%s not present (backend-only checkout)", typesPath) + t.Skipf("%s not present (backend-only checkout)", frontendTypesDir) } - t.Fatalf("read %s: %v", typesPath, err) + t.Fatalf("read frontend types: %v", err) } block := regexp.MustCompile(`(?s)export const MODES\s*:[^=]*=\s*\[(.*?)\]`).FindSubmatch(raw) if block == nil { - t.Fatalf("MODES array not found in %s", typesPath) + t.Fatalf("MODES array not found under %s", frontendTypesDir) } quoted := regexp.MustCompile(`"([^"]+)"`).FindAllStringSubmatch(string(block[1]), -1) frontend := make([]string, 0, len(quoted))