|
| 1 | +import { describe, it, expect, vi, beforeEach, afterEach } from "vitest"; |
| 2 | +import * as path from "path"; |
| 3 | + |
| 4 | +// ESM mocks for internal modules used by _loader-utils |
| 5 | +vi.mock("./utils/module-params", () => { |
| 6 | + return { |
| 7 | + parseParametrizedModuleId: vi.fn((rawId: string) => { |
| 8 | + const url = new URL(rawId, "module://"); |
| 9 | + return { |
| 10 | + id: url.pathname.replace(/^\//, ""), |
| 11 | + params: Object.fromEntries(url.searchParams.entries()), |
| 12 | + }; |
| 13 | + }), |
| 14 | + }; |
| 15 | +}); |
| 16 | + |
| 17 | +vi.mock("./lib/lcp", () => { |
| 18 | + return { |
| 19 | + LCP: { |
| 20 | + ready: vi.fn(async () => undefined), |
| 21 | + getInstance: vi.fn(() => ({ data: { version: 0.1 } })), |
| 22 | + }, |
| 23 | + }; |
| 24 | +}); |
| 25 | + |
| 26 | +vi.mock("./lib/lcp/server", () => { |
| 27 | + return { |
| 28 | + LCPServer: { |
| 29 | + loadDictionaries: vi.fn(async () => ({})), |
| 30 | + }, |
| 31 | + }; |
| 32 | +}); |
| 33 | + |
| 34 | +// Import under test AFTER mocks |
| 35 | +import { loadDictionary, transformComponent } from "./_loader-utils"; |
| 36 | +import { defaultParams } from "./_base"; |
| 37 | + |
| 38 | +describe("loadDictionary", () => { |
| 39 | + beforeEach(async () => { |
| 40 | + const lcpMod = await import("./lib/lcp"); |
| 41 | + (lcpMod.LCP.ready as any).mockClear(); |
| 42 | + (lcpMod.LCP.getInstance as any).mockClear(); |
| 43 | + const serverMod = await import("./lib/lcp/server"); |
| 44 | + (serverMod.LCPServer.loadDictionaries as any).mockClear(); |
| 45 | + }); |
| 46 | + |
| 47 | + it("returns null when path is not a dictionary file", async () => { |
| 48 | + const result = await loadDictionary({ |
| 49 | + resourcePath: "/project/src/lingo/not-dictionary.tsx", |
| 50 | + resourceQuery: "", |
| 51 | + params: {}, |
| 52 | + sourceRoot: "src", |
| 53 | + lingoDir: "lingo", |
| 54 | + isDev: false, |
| 55 | + }); |
| 56 | + expect(result).toBeNull(); |
| 57 | + const lcpMod = await import("./lib/lcp"); |
| 58 | + expect(lcpMod.LCP.ready).not.toHaveBeenCalled(); |
| 59 | + }); |
| 60 | + |
| 61 | + it("returns null when locale param is missing", async () => { |
| 62 | + // Override parser to drop locale |
| 63 | + const parseMod = await import("./utils/module-params"); |
| 64 | + (parseMod.parseParametrizedModuleId as any).mockImplementation( |
| 65 | + (rawId: string) => ({ id: rawId, params: {} }), |
| 66 | + ); |
| 67 | + |
| 68 | + const result = await loadDictionary({ |
| 69 | + resourcePath: "/project/src/lingo/dictionary.js", |
| 70 | + resourceQuery: "", |
| 71 | + params: {}, |
| 72 | + sourceRoot: "src", |
| 73 | + lingoDir: "lingo", |
| 74 | + isDev: false, |
| 75 | + }); |
| 76 | + expect(result).toBeNull(); |
| 77 | + const lcpMod = await import("./lib/lcp"); |
| 78 | + expect(lcpMod.LCP.ready).not.toHaveBeenCalled(); |
| 79 | + }); |
| 80 | + |
| 81 | + it("loads dictionary for provided locale and passes params to server", async () => { |
| 82 | + // Restore default module param parser |
| 83 | + const parseMod = await import("./utils/module-params"); |
| 84 | + (parseMod.parseParametrizedModuleId as any).mockImplementation( |
| 85 | + (rawId: string) => { |
| 86 | + const url = new URL(rawId, "module://"); |
| 87 | + return { |
| 88 | + id: url.pathname.replace(/^\//, ""), |
| 89 | + params: Object.fromEntries(url.searchParams.entries()), |
| 90 | + }; |
| 91 | + }, |
| 92 | + ); |
| 93 | + |
| 94 | + const DICT = { version: 0.1, locale: "es", files: {} }; |
| 95 | + const serverMod = await import("./lib/lcp/server"); |
| 96 | + (serverMod.LCPServer.loadDictionaries as any).mockResolvedValueOnce({ |
| 97 | + es: DICT, |
| 98 | + }); |
| 99 | + |
| 100 | + const result = await loadDictionary({ |
| 101 | + resourcePath: "/project/src/lingo/dictionary.js", |
| 102 | + resourceQuery: "?locale=es", |
| 103 | + params: { sourceLocale: "en", targetLocales: ["es"], foo: "bar" }, |
| 104 | + sourceRoot: "src", |
| 105 | + lingoDir: "lingo", |
| 106 | + isDev: true, |
| 107 | + }); |
| 108 | + |
| 109 | + expect(result).toEqual(DICT); |
| 110 | + const lcpMod = await import("./lib/lcp"); |
| 111 | + expect(lcpMod.LCP.ready).toHaveBeenCalledWith({ |
| 112 | + sourceRoot: "src", |
| 113 | + lingoDir: "lingo", |
| 114 | + isDev: true, |
| 115 | + }); |
| 116 | + expect(lcpMod.LCP.getInstance).toHaveBeenCalledWith({ |
| 117 | + sourceRoot: "src", |
| 118 | + lingoDir: "lingo", |
| 119 | + isDev: true, |
| 120 | + }); |
| 121 | + expect(serverMod.LCPServer.loadDictionaries).toHaveBeenCalledWith({ |
| 122 | + sourceLocale: "en", |
| 123 | + targetLocales: ["es"], |
| 124 | + foo: "bar", |
| 125 | + lcp: { version: 0.1 }, |
| 126 | + }); |
| 127 | + }); |
| 128 | + |
| 129 | + it("throws when dictionary for locale is missing", async () => { |
| 130 | + const serverMod = await import("./lib/lcp/server"); |
| 131 | + (serverMod.LCPServer.loadDictionaries as any).mockResolvedValueOnce({}); |
| 132 | + await expect( |
| 133 | + loadDictionary({ |
| 134 | + resourcePath: "/project/src/lingo/dictionary.js", |
| 135 | + resourceQuery: "?locale=fr", |
| 136 | + params: { sourceLocale: "en", targetLocales: ["fr"] }, |
| 137 | + sourceRoot: "src", |
| 138 | + lingoDir: "lingo", |
| 139 | + isDev: false, |
| 140 | + }), |
| 141 | + ).rejects.toThrow('Dictionary for locale "fr" could not be generated.'); |
| 142 | + }); |
| 143 | +}); |
| 144 | + |
| 145 | +describe("transformComponent", () => { |
| 146 | + it("returns the same code when nothing to transform and normalizes relativeFilePath", () => { |
| 147 | + const code = "export const X = 1;"; |
| 148 | + const result = transformComponent({ |
| 149 | + code, |
| 150 | + params: defaultParams, |
| 151 | + resourcePath: path.join("/project", "src", "deep", "file.tsx"), |
| 152 | + sourceRoot: "src", |
| 153 | + }); |
| 154 | + expect(result.code).toContain("export const X = 1;"); |
| 155 | + // sanity: should return a code+map object |
| 156 | + expect(result.map).toBeDefined(); |
| 157 | + }); |
| 158 | +}); |
0 commit comments