|
3 | 3 | import unittest |
4 | 4 |
|
5 | 5 | from repokg.code import walk |
6 | | -from repokg.deps import _js_configs, _jsonc_loads, collect |
| 6 | +from repokg.deps import _js_configs, _js_workspaces, _jsonc_loads, _pnpm_globs, collect |
7 | 7 |
|
8 | 8 |
|
9 | 9 | def write(root, rel, text): |
@@ -205,5 +205,119 @@ def test_jsonc_config_with_comments_and_trailing_commas(self): |
205 | 205 | self.assertEqual(self.edges(), {("app", "src"): 1}) |
206 | 206 |
|
207 | 207 |
|
| 208 | +class TestJsWorkspaces(unittest.TestCase): |
| 209 | + def setUp(self): |
| 210 | + self.tmp = tempfile.TemporaryDirectory() |
| 211 | + self.repo = self.tmp.name |
| 212 | + |
| 213 | + def tearDown(self): |
| 214 | + self.tmp.cleanup() |
| 215 | + |
| 216 | + def tree(self): |
| 217 | + return dict(walk(self.repo)) |
| 218 | + |
| 219 | + def test_npm_array_and_scoped_names(self): |
| 220 | + write(self.repo, "package.json", |
| 221 | + '{"name": "root", "workspaces": ["packages/*"]}') |
| 222 | + write(self.repo, "packages/core/package.json", |
| 223 | + '{"name": "@acme/core"}') |
| 224 | + write(self.repo, "packages/ui/package.json", |
| 225 | + '{"name": "@acme/ui"}') |
| 226 | + self.assertEqual(_js_workspaces(self.repo, self.tree()), |
| 227 | + {"@acme/core": "packages/core", |
| 228 | + "@acme/ui": "packages/ui"}) |
| 229 | + |
| 230 | + def test_yarn_object_form(self): |
| 231 | + write(self.repo, "package.json", |
| 232 | + '{"workspaces": {"packages": ["libs/*"], "nohoist": ["**"]}}') |
| 233 | + write(self.repo, "libs/log/package.json", '{"name": "log"}') |
| 234 | + self.assertEqual(_js_workspaces(self.repo, self.tree()), |
| 235 | + {"log": "libs/log"}) |
| 236 | + |
| 237 | + def test_pnpm_workspace_yaml(self): |
| 238 | + write(self.repo, "pnpm-workspace.yaml", |
| 239 | + "# workspace layout\n" |
| 240 | + "packages:\n" |
| 241 | + " - 'packages/*'\n" |
| 242 | + ' - "apps/*"\n' |
| 243 | + " - tools\n" |
| 244 | + "catalog:\n" |
| 245 | + " - not-a-glob\n") |
| 246 | + write(self.repo, "packages/core/package.json", '{"name": "@acme/core"}') |
| 247 | + write(self.repo, "apps/web/package.json", '{"name": "web"}') |
| 248 | + write(self.repo, "tools/package.json", '{"name": "tools"}') |
| 249 | + self.assertEqual(_js_workspaces(self.repo, self.tree()), |
| 250 | + {"@acme/core": "packages/core", "web": "apps/web", |
| 251 | + "tools": "tools"}) |
| 252 | + |
| 253 | + def test_pnpm_globs_parser(self): |
| 254 | + self.assertEqual( |
| 255 | + _pnpm_globs("packages:\n - 'a/*'\n - b\nother:\n - c\n"), |
| 256 | + ["a/*", "b"]) |
| 257 | + self.assertEqual(_pnpm_globs("onlyOther:\n - c\n"), []) |
| 258 | + |
| 259 | + def test_star_glob_does_not_cross_segments(self): |
| 260 | + write(self.repo, "package.json", '{"workspaces": ["packages/*"]}') |
| 261 | + write(self.repo, "packages/core/package.json", '{"name": "core"}') |
| 262 | + write(self.repo, "packages/core/examples/demo/package.json", |
| 263 | + '{"name": "demo"}') |
| 264 | + self.assertEqual(_js_workspaces(self.repo, self.tree()), |
| 265 | + {"core": "packages/core"}) |
| 266 | + |
| 267 | + def test_double_star_glob_crosses_segments(self): |
| 268 | + write(self.repo, "package.json", '{"workspaces": ["libs/**"]}') |
| 269 | + write(self.repo, "libs/a/deep/pkg/package.json", '{"name": "deep"}') |
| 270 | + self.assertEqual(_js_workspaces(self.repo, self.tree()), |
| 271 | + {"deep": "libs/a/deep/pkg"}) |
| 272 | + |
| 273 | + def test_workspace_without_name_ignored(self): |
| 274 | + write(self.repo, "package.json", '{"workspaces": ["packages/*"]}') |
| 275 | + write(self.repo, "packages/anon/package.json", '{"private": true}') |
| 276 | + self.assertEqual(_js_workspaces(self.repo, self.tree()), {}) |
| 277 | + |
| 278 | + |
| 279 | +class TestJsWorkspaceEdges(unittest.TestCase): |
| 280 | + def setUp(self): |
| 281 | + self.tmp = tempfile.TemporaryDirectory() |
| 282 | + self.repo = self.tmp.name |
| 283 | + write(self.repo, "package.json", '{"workspaces": ["packages/*"]}') |
| 284 | + write(self.repo, "packages/core/package.json", '{"name": "@acme/core"}') |
| 285 | + write(self.repo, "packages/core/src/index.ts", "export const c = 1\n") |
| 286 | + |
| 287 | + def tearDown(self): |
| 288 | + self.tmp.cleanup() |
| 289 | + |
| 290 | + def edges(self): |
| 291 | + return {(e["from"], e["to"]): e["count"] |
| 292 | + for e in collect(self.repo) if e["lang"] == "JS/TS"} |
| 293 | + |
| 294 | + def test_workspace_name_import(self): |
| 295 | + write(self.repo, "packages/app/package.json", '{"name": "@acme/app"}') |
| 296 | + write(self.repo, "packages/app/main.ts", |
| 297 | + "import { c } from '@acme/core'\n" |
| 298 | + "import React from 'react'\n") |
| 299 | + self.assertEqual(self.edges(), |
| 300 | + {("packages/app", "packages/core"): 1}) |
| 301 | + |
| 302 | + def test_workspace_subpath_grounds_or_falls_back(self): |
| 303 | + write(self.repo, "packages/app/package.json", '{"name": "@acme/app"}') |
| 304 | + write(self.repo, "packages/app/main.ts", |
| 305 | + "import { c } from '@acme/core/src/index'\n" # grounds in src/ |
| 306 | + "import { d } from '@acme/core/dist/util'\n") # falls back to pkg dir |
| 307 | + self.assertEqual(self.edges(), { |
| 308 | + ("packages/app", "packages/core/src"): 1, |
| 309 | + ("packages/app", "packages/core"): 1, |
| 310 | + }) |
| 311 | + |
| 312 | + def test_paths_alias_takes_precedence_over_workspace(self): |
| 313 | + write(self.repo, "shim/core.ts", "export const c = 2\n") |
| 314 | + write(self.repo, "packages/app/tsconfig.json", |
| 315 | + '{"compilerOptions": {"paths": {"@acme/core": ["../../shim/core.ts"]}}}') |
| 316 | + write(self.repo, "packages/app/package.json", '{"name": "@acme/app"}') |
| 317 | + write(self.repo, "packages/app/main.ts", |
| 318 | + "import { c } from '@acme/core'\n") |
| 319 | + self.assertEqual(self.edges(), {("packages/app", "shim"): 1}) |
| 320 | + |
| 321 | + |
208 | 322 | if __name__ == "__main__": |
209 | 323 | unittest.main() |
0 commit comments