project-yaml.test.ts 5.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127
  1. import { mkdir, mkdtemp, rm } from "node:fs/promises";
  2. import { tmpdir } from "node:os";
  3. import { join } from "node:path";
  4. import { describe, expect, it } from "vitest";
  5. import {
  6. PROJECT_YAML_NAME,
  7. projectYamlSchema,
  8. readProjectEnabledMods,
  9. readProjectYaml,
  10. writeProjectYaml,
  11. } from "../src/project-yaml.js";
  12. async function tmpDir(): Promise<string> {
  13. return mkdtemp(join(tmpdir(), "editor-yaml-"));
  14. }
  15. describe("projectYamlSchema", () => {
  16. it("accepts empty and partial metadata", () => {
  17. expect(projectYamlSchema.safeParse({}).success).toBe(true);
  18. expect(projectYamlSchema.safeParse({ name: "Game" }).success).toBe(true);
  19. expect(
  20. projectYamlSchema.safeParse({ name: "Game", icon: "icon.png", pack: { installer: true, splitAssets: true } })
  21. .success,
  22. ).toBe(true);
  23. });
  24. it("accepts the F1 mods field (absent / empty / enabled list)", () => {
  25. expect(projectYamlSchema.safeParse({ mods: {} }).success).toBe(true);
  26. expect(projectYamlSchema.safeParse({ mods: { enabled: [] } }).success).toBe(true);
  27. expect(projectYamlSchema.safeParse({ mods: { enabled: ["a-mod", "b-mod"] } }).success).toBe(true);
  28. expect(projectYamlSchema.safeParse({ name: "G", mods: { enabled: ["a-mod"] } }).success).toBe(true);
  29. });
  30. it("rejects unknown keys and wrong types", () => {
  31. expect(projectYamlSchema.safeParse({ nmae: "Game" }).success).toBe(false);
  32. expect(projectYamlSchema.safeParse({ name: 42 }).success).toBe(false);
  33. expect(projectYamlSchema.safeParse({ pack: { installer: "yes" } }).success).toBe(false);
  34. expect(projectYamlSchema.safeParse({ pack: { unknown: true } }).success).toBe(false);
  35. expect(projectYamlSchema.safeParse({ pack: "installed" }).success).toBe(false);
  36. expect(projectYamlSchema.safeParse({ mods: { enabled: [1] } }).success).toBe(false);
  37. expect(projectYamlSchema.safeParse({ mods: { enabled: "" } }).success).toBe(false);
  38. expect(projectYamlSchema.safeParse({ mods: { unknown: true } }).success).toBe(false);
  39. expect(projectYamlSchema.safeParse({ mods: "all" }).success).toBe(false);
  40. });
  41. });
  42. describe("readProjectYaml / writeProjectYaml", () => {
  43. it("returns null when project.yaml is absent", async () => {
  44. const dir = await tmpDir();
  45. try {
  46. expect(await readProjectYaml(dir)).toBeNull();
  47. } finally {
  48. await rm(dir, { recursive: true, force: true });
  49. }
  50. });
  51. it("roundtrips a written project.yaml", async () => {
  52. const dir = await tmpDir();
  53. try {
  54. const data = { name: "Pinefield", icon: "icon.png", pack: { installer: true, mods: true, splitAssets: true } };
  55. await writeProjectYaml(dir, data);
  56. expect(await readProjectYaml(dir)).toEqual(data);
  57. const { readFile } = await import("node:fs/promises");
  58. const raw = await readFile(join(dir, PROJECT_YAML_NAME), "utf8");
  59. expect(raw).toContain("name: Pinefield");
  60. } finally {
  61. await rm(dir, { recursive: true, force: true });
  62. }
  63. });
  64. it("roundtrips the mods field losslessly with other fields preserved", async () => {
  65. const dir = await tmpDir();
  66. try {
  67. const data = {
  68. name: "Pinefield",
  69. pack: { mods: true },
  70. mods: { enabled: ["fishing-mod", "weather-mod"] },
  71. };
  72. await writeProjectYaml(dir, data);
  73. expect(await readProjectYaml(dir)).toEqual(data);
  74. } finally {
  75. await rm(dir, { recursive: true, force: true });
  76. }
  77. });
  78. it("roundtrips an empty enabled list (all disabled) as distinct from absent", async () => {
  79. const dir = await tmpDir();
  80. try {
  81. await writeProjectYaml(dir, { mods: { enabled: [] } });
  82. const read = await readProjectYaml(dir);
  83. expect(read).toEqual({ mods: { enabled: [] } });
  84. expect(read?.mods?.enabled).toEqual([]);
  85. } finally {
  86. await rm(dir, { recursive: true, force: true });
  87. }
  88. });
  89. it("readProjectEnabledMods returns undefined when absent or malformed", async () => {
  90. const dir = await tmpDir();
  91. try {
  92. expect(await readProjectEnabledMods(dir)).toBeUndefined();
  93. await writeProjectYaml(dir, { mods: { enabled: ["a-mod"] } });
  94. expect(await readProjectEnabledMods(dir)).toEqual(["a-mod"]);
  95. await writeProjectYaml(dir, {});
  96. expect(await readProjectEnabledMods(dir)).toBeUndefined();
  97. const { writeFile } = await import("node:fs/promises");
  98. await writeFile(join(dir, PROJECT_YAML_NAME), "name: [broken\n", "utf8");
  99. expect(await readProjectEnabledMods(dir)).toBeUndefined();
  100. } finally {
  101. await rm(dir, { recursive: true, force: true });
  102. }
  103. });
  104. it("throws on malformed yaml and invalid data", async () => {
  105. const dir = await tmpDir();
  106. try {
  107. await mkdir(dir, { recursive: true });
  108. const { writeFile } = await import("node:fs/promises");
  109. await writeFile(join(dir, PROJECT_YAML_NAME), "name: [unclosed\n", "utf8");
  110. await expect(readProjectYaml(dir)).rejects.toThrow(/invalid project.yaml/);
  111. await expect(writeProjectYaml(dir, { name: 42 } as never)).rejects.toThrow(/invalid project.yaml/);
  112. } finally {
  113. await rm(dir, { recursive: true, force: true });
  114. }
  115. });
  116. });