@testspectra/cli 1.0.70 → 1.1.0-rc.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE.md +48 -0
- package/README.md +36 -270
- package/bin/spectra.js +1 -1
- package/bin/testspectra-runner +0 -0
- package/demo-app/assets/index-BGllUp7o.js +140 -0
- package/demo-app/assets/index-BhlW-eXG.css +1 -0
- package/demo-app/index.html +2 -2
- package/dist/.tsbuildinfo +1 -0
- package/dist/commands/__tests__/doctor.test.d.ts +1 -0
- package/dist/commands/__tests__/doctor.test.js +39 -0
- package/dist/commands/__tests__/init.test.d.ts +1 -0
- package/dist/commands/__tests__/init.test.js +148 -0
- package/dist/commands/__tests__/license.test.d.ts +1 -0
- package/dist/commands/__tests__/license.test.js +13 -0
- package/dist/commands/__tests__/refactor.test.d.ts +1 -0
- package/dist/commands/__tests__/refactor.test.js +64 -0
- package/dist/commands/__tests__/run-e2e.test.d.ts +1 -0
- package/dist/commands/__tests__/run-e2e.test.js +422 -0
- package/dist/commands/__tests__/run.test.d.ts +1 -0
- package/dist/commands/__tests__/run.test.js +277 -0
- package/dist/commands/__tests__/watch.test.d.ts +1 -0
- package/dist/commands/__tests__/watch.test.js +155 -0
- package/dist/commands/add.js +86 -85
- package/dist/commands/demo.js +21 -11
- package/dist/commands/devices.d.ts +14 -2
- package/dist/commands/devices.js +44 -34
- package/dist/commands/doctor.d.ts +3 -1
- package/dist/commands/doctor.js +43 -264
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.js +206 -196
- package/dist/commands/install.d.ts +4 -0
- package/dist/commands/install.js +59 -0
- package/dist/commands/license.d.ts +4 -0
- package/dist/commands/license.js +30 -0
- package/dist/commands/lint.d.ts +5 -0
- package/dist/commands/lint.js +55 -0
- package/dist/commands/refactor.d.ts +9 -0
- package/dist/commands/refactor.js +172 -0
- package/dist/commands/run.d.ts +23 -2
- package/dist/commands/run.js +321 -111
- package/dist/commands/skills.d.ts +9 -0
- package/dist/commands/skills.js +28 -0
- package/dist/commands/sync-types.d.ts +4 -0
- package/dist/commands/sync-types.js +14 -0
- package/dist/commands/update.js +39 -43
- package/dist/commands/watch.d.ts +6 -0
- package/dist/commands/watch.js +46 -43
- package/dist/config/loader.d.ts +1 -1
- package/dist/config/loader.js +40 -25
- package/dist/config/schema.d.ts +117 -54
- package/dist/config/schema.js +41 -35
- package/dist/generator/__tests__/tsconfig-isolation.test.d.ts +1 -0
- package/dist/generator/__tests__/tsconfig-isolation.test.js +278 -0
- package/dist/generator/__tests__/type-generator.test.d.ts +1 -0
- package/dist/generator/__tests__/type-generator.test.js +386 -0
- package/dist/generator/index.d.ts +2 -0
- package/dist/generator/index.js +2 -0
- package/dist/generator/tsconfig-generator.d.ts +27 -0
- package/dist/generator/tsconfig-generator.js +384 -0
- package/dist/generator/type-generator.d.ts +36 -0
- package/dist/generator/type-generator.js +644 -0
- package/dist/index.d.ts +9 -5
- package/dist/index.js +151 -51
- package/dist/lifecycle/__tests__/lifecycle-engine.test.d.ts +1 -0
- package/dist/lifecycle/__tests__/lifecycle-engine.test.js +290 -0
- package/dist/lifecycle/hook-discovery.d.ts +24 -0
- package/dist/lifecycle/hook-discovery.js +60 -0
- package/dist/lifecycle/hook-dispatcher.d.ts +34 -0
- package/dist/lifecycle/hook-dispatcher.js +190 -0
- package/dist/lifecycle/index.d.ts +3 -0
- package/dist/lifecycle/index.js +3 -0
- package/dist/lifecycle/parallel-grouping.d.ts +27 -0
- package/dist/lifecycle/parallel-grouping.js +151 -0
- package/dist/linter/__tests__/anonymous-export.test.d.ts +1 -0
- package/dist/linter/__tests__/anonymous-export.test.js +184 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.d.ts +1 -0
- package/dist/linter/__tests__/shared-lib-boundary.test.js +270 -0
- package/dist/linter/__tests__/spec-schema.test.d.ts +1 -0
- package/dist/linter/__tests__/spec-schema.test.js +369 -0
- package/dist/linter/__tests__/step-parity.test.d.ts +1 -0
- package/dist/linter/__tests__/step-parity.test.js +372 -0
- package/dist/linter/discovery.d.ts +30 -0
- package/dist/linter/discovery.js +249 -0
- package/dist/linter/extractor.d.ts +66 -0
- package/dist/linter/extractor.js +235 -0
- package/dist/linter/index.d.ts +7 -0
- package/dist/linter/index.js +7 -0
- package/dist/linter/parser.d.ts +24 -0
- package/dist/linter/parser.js +219 -0
- package/dist/linter/schemas/spec.schema.d.ts +83 -0
- package/dist/linter/schemas/spec.schema.js +150 -0
- package/dist/linter/schemas/suite.schema.d.ts +59 -0
- package/dist/linter/schemas/suite.schema.js +83 -0
- package/dist/linter/spec-linter.d.ts +23 -0
- package/dist/linter/spec-linter.js +595 -0
- package/dist/linter/types.d.ts +28 -0
- package/dist/linter/types.js +1 -0
- package/dist/mobile/__tests__/android-device-e2e.test.d.ts +1 -0
- package/dist/mobile/__tests__/android-device-e2e.test.js +53 -0
- package/dist/mobile/__tests__/device-pool.test.d.ts +1 -0
- package/dist/mobile/__tests__/device-pool.test.js +97 -0
- package/dist/mobile/device-pool.d.ts +23 -0
- package/dist/mobile/device-pool.js +1 -0
- package/dist/mobile/index.d.ts +1 -0
- package/dist/mobile/index.js +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.d.ts +1 -0
- package/dist/refactor/__tests__/refactor-engine.test.js +873 -0
- package/dist/refactor/index.d.ts +1 -0
- package/dist/refactor/index.js +1 -0
- package/dist/refactor/refactor-engine.d.ts +50 -0
- package/dist/refactor/refactor-engine.js +587 -0
- package/dist/reporter/__tests__/semantic-formatter.test.d.ts +1 -0
- package/dist/reporter/__tests__/semantic-formatter.test.js +73 -0
- package/dist/reporter/semantic-formatter.d.ts +6 -0
- package/dist/reporter/semantic-formatter.js +191 -0
- package/dist/reporter/summary-view.d.ts +12 -0
- package/dist/reporter/summary-view.js +19 -0
- package/dist/reporter/terminal-stream.d.ts +32 -0
- package/dist/reporter/terminal-stream.js +82 -0
- package/dist/reporter/types.d.ts +2 -0
- package/dist/reporter/types.js +1 -0
- package/dist/runner/bridge.d.ts +10 -5
- package/dist/runner/bridge.js +101 -45
- package/dist/runner/reporter.d.ts +32 -2
- package/dist/runner/reporter.js +423 -13
- package/dist/server/demo.js +43 -44
- package/dist/types/generator.d.ts +1 -17
- package/dist/types/generator.js +1 -443
- package/dist/utils/__tests__/api-server.test.d.ts +1 -0
- package/dist/utils/__tests__/api-server.test.js +62 -0
- package/dist/utils/__tests__/demo-server.test.d.ts +1 -0
- package/dist/utils/__tests__/demo-server.test.js +35 -0
- package/dist/utils/api-server.d.ts +19 -0
- package/dist/utils/api-server.js +134 -0
- package/dist/utils/demo-server.d.ts +5 -0
- package/dist/utils/demo-server.js +46 -36
- package/package.json +24 -13
- package/templates/default/CLAUDE.md +12 -5
- package/templates/default/fixtures/apiData.json +24 -0
- package/templates/default/package.json +12 -3
- package/templates/default/page-objects/MatchersPage/mobile.ts +53 -17
- package/templates/default/page-objects/MatchersPage/web.ts +17 -2
- package/templates/default/page-objects/NavigationPage/mobile.ts +11 -2
- package/templates/default/page-objects/NavigationPage/web.ts +1 -1
- package/templates/default/page-objects/PlaygroundPage/mobile.ts +28 -18
- package/templates/default/page-objects/PlaygroundPage/web.ts +4 -2
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +53 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +19 -0
- package/templates/default/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +31 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/android.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/common.test.ts +3 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/spec.md +19 -0
- package/templates/default/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/common.test.ts +33 -0
- package/templates/default/specs/ApiInterception/TC-0012-direct-and-late-response/spec.md +22 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/common.test.ts +27 -0
- package/templates/default/specs/ApiInterception/TC-0013-real-network-call-recorded/spec.md +20 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/spec.md +24 -0
- package/templates/default/specs/ApiInterception/TC-0015-mock-server-sent-events/web.test.ts +26 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/spec.md +37 -0
- package/templates/default/specs/ApiInterception/TC-0016-bypass-websocket-with-app-level-mock/web.test.ts +21 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/spec.md +29 -0
- package/templates/default/specs/ApiInterception/TC-0017-bypass-captcha-with-test-mode-flag/web.test.ts +16 -0
- package/templates/default/specs/ApiInterception/suite.md +10 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/common.test.ts +30 -0
- package/templates/default/specs/ApiSeeding/TC-0014-seed-and-cleanup-via-hooks/spec.md +23 -0
- package/templates/default/specs/ApiSeeding/hooks/after/common.hook.ts +16 -0
- package/templates/default/specs/ApiSeeding/hooks/before/common.hook.ts +28 -0
- package/templates/default/specs/ApiSeeding/suite.md +9 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +7 -1
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +5 -0
- package/templates/default/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/common.test.ts +26 -0
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/spec.md +7 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +9 -1
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +5 -0
- package/templates/default/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/mobile.test.ts +28 -0
- package/templates/default/specs/DeepLink/TC-0018-navigate-via-deep-link/spec.md +29 -0
- package/templates/default/specs/DeepLink/suite.md +9 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +9 -1
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +5 -0
- package/templates/default/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +27 -1
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +7 -0
- package/templates/default/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/default/specs/ElementMatchers/suite.md +2 -1
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/mobile.test.ts +26 -0
- package/templates/default/specs/PermissionRationale/TC-0010-camera-permission-allow/spec.md +22 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/mobile.test.ts +13 -0
- package/templates/default/specs/PermissionRationale/TC-0011-camera-permission-deny/spec.md +20 -0
- package/templates/default/specs/PermissionRationale/suite.md +9 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0001-form-controls/spec.md +6 -0
- package/templates/default/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
- package/templates/default/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +4 -1
- package/templates/default/specs/Playground/TC-0002-toggle-elements/spec.md +6 -0
- package/templates/default/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
- package/templates/default/spectra.config.ts +21 -15
- package/templates/default/support/actions/fillCredentials/mobile.action.ts +3 -3
- package/templates/default/support/actions/fillCredentials/web.action.ts +1 -1
- package/templates/default/support/steps/togglePlaygroundStates/mobile.step.ts +3 -1
- package/templates/default/support/steps/togglePlaygroundStates/web.step.ts +3 -3
- package/templates/default/support/steps/verifyPlaygroundState/mobile.step.ts +4 -2
- package/templates/default/support/steps/verifyPlaygroundState/web.step.ts +4 -4
- package/templates/default/tsconfig.json +3 -1
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db +0 -0
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-shm +0 -0
- package/templates/nx/.nx/workspace-data/70094CFD-E89B-57A1-9619-2FC5F4963C54.db-wal +0 -0
- package/templates/nx/.nx/workspace-data/d/daemon.log +4306 -0
- package/templates/nx/.nx/workspace-data/d/server-process.json +3 -0
- package/templates/nx/.nx/workspace-data/file-map.json +452 -0
- package/templates/nx/.nx/workspace-data/lockfile.hash +1 -0
- package/templates/nx/.nx/workspace-data/nx_files.nxt +0 -0
- package/templates/nx/.nx/workspace-data/parsed-lock-file.json +13485 -0
- package/templates/nx/.nx/workspace-data/project-graph.json +14389 -0
- package/templates/nx/CLAUDE.md +2 -2
- package/templates/nx/package.json +6 -4
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/mobile.test.ts +27 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0008-fetch-mocking/web.test.ts +27 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/mobile.test.ts +24 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/spec.md +12 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/TC-0009-post-mocking/web.test.ts +24 -0
- package/templates/nx/packages/matchers/e2e/specs/ApiInterception/suite.md +9 -0
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +17 -3
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +15 -5
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +9 -4
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +9 -3
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +1 -1
- package/templates/nx/packages/matchers/e2e/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +27 -6
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/mobile.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0001-form-controls/web.test.ts +4 -4
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +1 -1
- package/templates/nx/packages/playground/e2e/specs/Playground/TC-0002-toggle-elements/web.test.ts +3 -3
- package/templates/nx/pnpm-lock.yaml +6663 -0
- package/templates/nx/pnpm-workspace.yaml +2 -1
- package/templates/nx/shared/testing/fixtures/apiData.json +24 -0
- package/templates/nx/shared/testing/page-objects/MatchersPage/mobile.ts +13 -1
- package/templates/nx/shared/testing/page-objects/MatchersPage/web.ts +85 -17
- package/templates/nx/shared/testing/page-objects/NavigationPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/NavigationPage/web.ts +1 -1
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/mobile.ts +1 -1
- package/templates/nx/shared/testing/page-objects/PlaygroundPage/web.ts +4 -2
- package/templates/nx/shared/testing/project.json +19 -0
- package/templates/nx/shared/testing/support/actions/fillCredentials/mobile.action.ts +1 -1
- package/templates/nx/shared/testing/support/actions/fillCredentials/web.action.ts +1 -1
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/mobile.step.ts +1 -1
- package/templates/nx/shared/testing/support/steps/togglePlaygroundStates/web.step.ts +3 -3
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/mobile.step.ts +2 -2
- package/templates/nx/shared/testing/support/steps/verifyPlaygroundState/web.step.ts +4 -4
- package/templates/nx/spectra.config.ts +19 -13
- package/templates/nx/tsconfig.json +3 -1
- package/demo-app/assets/index-DAUlrHTr.css +0 -1
- package/demo-app/assets/index-DN2Kb0A8.js +0 -100
- package/dist/index.cjs +0 -101
- package/dist/plugin.cjs +0 -575
- package/dist/plugin.d.ts +0 -16
- package/dist/plugin.js +0 -184
- package/dist/step/index.d.ts +0 -6
- package/dist/step/index.js +0 -6
- package/dist/step/matchers.d.ts +0 -14
- package/dist/step/matchers.js +0 -114
- package/dist/step/proto.d.ts +0 -46
- package/dist/step/proto.js +0 -114
- package/dist/step/runner/collection.d.ts +0 -13
- package/dist/step/runner/collection.js +0 -51
- package/dist/step/runner/single.d.ts +0 -21
- package/dist/step/runner/single.js +0 -138
- package/dist/step/spectra.d.ts +0 -34
- package/dist/step/spectra.js +0 -156
- package/dist/step/types.d.ts +0 -32
- package/dist/step/types.js +0 -5
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
- package/templates/default/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
- package/templates/default/tsconfig.spectra.android.json +0 -46
- package/templates/default/tsconfig.spectra.ios.json +0 -46
- package/templates/default/tsconfig.spectra.json +0 -22
- package/templates/default/tsconfig.spectra.web.json +0 -53
- package/templates/nx/fixtures/playgroundData.json +0 -7
- package/templates/nx/global-hooks/after/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/after/web.hook.ts +0 -3
- package/templates/nx/global-hooks/before/mobile.hook.ts +0 -3
- package/templates/nx/global-hooks/before/web.hook.ts +0 -3
- package/templates/nx/page-objects/MatchersPage/mobile.ts +0 -21
- package/templates/nx/page-objects/MatchersPage/web.ts +0 -23
- package/templates/nx/page-objects/NavigationPage/mobile.ts +0 -5
- package/templates/nx/page-objects/NavigationPage/web.ts +0 -8
- package/templates/nx/page-objects/PlaygroundPage/mobile.ts +0 -26
- package/templates/nx/page-objects/PlaygroundPage/web.ts +0 -29
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/mobile.test.ts +0 -3
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/spec.md +0 -12
- package/templates/nx/specs/BrowserContext/TC-0007-title-and-navigation/web.test.ts +0 -5
- package/templates/nx/specs/BrowserContext/suite.md +0 -9
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0005-text-and-values/web.test.ts +0 -7
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/mobile.test.ts +0 -3
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/spec.md +0 -12
- package/templates/nx/specs/ContentMatchers/TC-0006-collections-and-empty/web.test.ts +0 -6
- package/templates/nx/specs/ContentMatchers/suite.md +0 -9
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0003-visibility-and-dom/web.test.ts +0 -5
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/mobile.test.ts +0 -3
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/spec.md +0 -12
- package/templates/nx/specs/ElementMatchers/TC-0004-states-and-attributes/web.test.ts +0 -8
- package/templates/nx/specs/ElementMatchers/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/ElementMatchers/suite.md +0 -9
- package/templates/nx/specs/Playground/TC-0001-form-controls/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0001-form-controls/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0001-form-controls/web.test.ts +0 -6
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/mobile.test.ts +0 -3
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/spec.md +0 -12
- package/templates/nx/specs/Playground/TC-0002-toggle-elements/web.test.ts +0 -6
- package/templates/nx/specs/Playground/hooks/after/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/after/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/afterEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/before/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/mobile.hook.ts +0 -1
- package/templates/nx/specs/Playground/hooks/beforeEach/web.hook.ts +0 -1
- package/templates/nx/specs/Playground/suite.md +0 -9
- package/templates/nx/support/actions/fillCredentials/mobile.action.ts +0 -4
- package/templates/nx/support/actions/fillCredentials/web.action.ts +0 -4
- package/templates/nx/support/steps/togglePlaygroundStates/mobile.step.ts +0 -3
- package/templates/nx/support/steps/togglePlaygroundStates/step.md +0 -7
- package/templates/nx/support/steps/togglePlaygroundStates/web.step.ts +0 -5
- package/templates/nx/support/steps/verifyPlaygroundState/mobile.step.ts +0 -4
- package/templates/nx/support/steps/verifyPlaygroundState/step.md +0 -7
- package/templates/nx/support/steps/verifyPlaygroundState/web.step.ts +0 -6
- package/templates/nx/tsconfig.spectra.android.json +0 -39
- package/templates/nx/tsconfig.spectra.ios.json +0 -39
- package/templates/nx/tsconfig.spectra.json +0 -25
- package/templates/nx/tsconfig.spectra.shared.json +0 -25
- package/templates/nx/tsconfig.spectra.web.json +0 -45
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/{nx/specs/BrowserContext → default/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/after/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/afterEach/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/before/web.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/mobile.hook.ts +0 -0
- /package/templates/nx/{specs/ContentMatchers → packages/matchers/e2e/specs/ApiInterception}/hooks/beforeEach/web.hook.ts +0 -0
package/dist/types/generator.js
CHANGED
|
@@ -1,443 +1 @@
|
|
|
1
|
-
|
|
2
|
-
import path from "path";
|
|
3
|
-
const PLATFORM_HIERARCHY = {
|
|
4
|
-
web: ["web", "common"],
|
|
5
|
-
ios: ["ios", "mobile", "common", "web"],
|
|
6
|
-
android: ["android", "mobile", "common", "web"],
|
|
7
|
-
mobile: ["mobile", "common", "android", "ios", "web"],
|
|
8
|
-
common: ["common", "web", "mobile", "android", "ios"],
|
|
9
|
-
};
|
|
10
|
-
export class TypeGenerator {
|
|
11
|
-
/**
|
|
12
|
-
* Discovers all feature directories and shared library directories.
|
|
13
|
-
* Returns a map of scope -> base directory (e.g. { "shared": ".../shared/testing", "auth": ".../packages/features/auth/e2e" })
|
|
14
|
-
*/
|
|
15
|
-
static getEntityScopes(cwd) {
|
|
16
|
-
const scopes = [];
|
|
17
|
-
// 1. Scan for shared directories
|
|
18
|
-
const sharedCandidates = [
|
|
19
|
-
path.join(cwd, "shared", "testing"),
|
|
20
|
-
path.join(cwd, "shared"),
|
|
21
|
-
path.join(cwd, "testing"),
|
|
22
|
-
];
|
|
23
|
-
for (const cand of sharedCandidates) {
|
|
24
|
-
if (fs.existsSync(cand) &&
|
|
25
|
-
(fs.existsSync(path.join(cand, "page-objects")) ||
|
|
26
|
-
fs.existsSync(path.join(cand, "support")) ||
|
|
27
|
-
fs.existsSync(path.join(cand, "steps")) ||
|
|
28
|
-
fs.existsSync(path.join(cand, "actions")) ||
|
|
29
|
-
fs.existsSync(path.join(cand, "fixtures")))) {
|
|
30
|
-
if (!scopes.some((s) => s.dir === cand)) {
|
|
31
|
-
scopes.push({ name: "shared", dir: cand, isShared: true });
|
|
32
|
-
}
|
|
33
|
-
}
|
|
34
|
-
}
|
|
35
|
-
// 2. Scan recursively for feature e2e directories
|
|
36
|
-
function scanForFeatures(dir, depth) {
|
|
37
|
-
if (depth <= 0 || !fs.existsSync(dir))
|
|
38
|
-
return;
|
|
39
|
-
const base = path.basename(dir);
|
|
40
|
-
if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
|
|
41
|
-
return;
|
|
42
|
-
if (base === "e2e" || base === "tests" || base === "specs") {
|
|
43
|
-
const featureDir = path.dirname(dir);
|
|
44
|
-
const featureName = path.basename(featureDir);
|
|
45
|
-
if (!scopes.some((s) => s.dir === dir)) {
|
|
46
|
-
scopes.push({ name: featureName, dir: dir, isShared: false });
|
|
47
|
-
}
|
|
48
|
-
return;
|
|
49
|
-
}
|
|
50
|
-
try {
|
|
51
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
52
|
-
for (const entry of entries) {
|
|
53
|
-
if (entry.isDirectory()) {
|
|
54
|
-
scanForFeatures(path.join(dir, entry.name), depth - 1);
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
}
|
|
58
|
-
catch { }
|
|
59
|
-
}
|
|
60
|
-
scanForFeatures(cwd, 6);
|
|
61
|
-
return scopes;
|
|
62
|
-
}
|
|
63
|
-
static getPageObjectsDirsForScope(scopeDir, sharedDir) {
|
|
64
|
-
const dirs = [];
|
|
65
|
-
function scanDir(dir, maxDepth) {
|
|
66
|
-
if (maxDepth <= 0 || !fs.existsSync(dir))
|
|
67
|
-
return;
|
|
68
|
-
const base = path.basename(dir);
|
|
69
|
-
if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
|
|
70
|
-
return;
|
|
71
|
-
if (base === "page-objects" || base === "pageobjects") {
|
|
72
|
-
if (!dirs.includes(dir))
|
|
73
|
-
dirs.push(dir);
|
|
74
|
-
return;
|
|
75
|
-
}
|
|
76
|
-
try {
|
|
77
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
78
|
-
for (const entry of entries) {
|
|
79
|
-
if (entry.isDirectory()) {
|
|
80
|
-
scanDir(path.join(dir, entry.name), maxDepth - 1);
|
|
81
|
-
}
|
|
82
|
-
}
|
|
83
|
-
}
|
|
84
|
-
catch { }
|
|
85
|
-
}
|
|
86
|
-
if (!sharedDir && (fs.existsSync(path.join(scopeDir, "pnpm-workspace.yaml")) || fs.existsSync(path.join(scopeDir, "nx.json")))) {
|
|
87
|
-
scanDir(scopeDir, 6);
|
|
88
|
-
}
|
|
89
|
-
else {
|
|
90
|
-
const candidates = [
|
|
91
|
-
path.join(scopeDir, "page-objects"),
|
|
92
|
-
path.join(scopeDir, "pageobjects"),
|
|
93
|
-
];
|
|
94
|
-
if (sharedDir) {
|
|
95
|
-
candidates.push(path.join(sharedDir, "page-objects"));
|
|
96
|
-
candidates.push(path.join(sharedDir, "pageobjects"));
|
|
97
|
-
}
|
|
98
|
-
for (const c of candidates) {
|
|
99
|
-
if (fs.existsSync(c) && !dirs.includes(c))
|
|
100
|
-
dirs.push(c);
|
|
101
|
-
}
|
|
102
|
-
}
|
|
103
|
-
return dirs;
|
|
104
|
-
}
|
|
105
|
-
static getEntityDirsForScope(entityType, scopeDir, sharedDir) {
|
|
106
|
-
const dirs = [];
|
|
107
|
-
function scanDir(dir, maxDepth) {
|
|
108
|
-
if (maxDepth <= 0 || !fs.existsSync(dir))
|
|
109
|
-
return;
|
|
110
|
-
const base = path.basename(dir);
|
|
111
|
-
if (base === "node_modules" || base === "dist" || base === ".git" || base === ".testspectra")
|
|
112
|
-
return;
|
|
113
|
-
if (base === entityType) {
|
|
114
|
-
if (!dirs.includes(dir))
|
|
115
|
-
dirs.push(dir);
|
|
116
|
-
return;
|
|
117
|
-
}
|
|
118
|
-
try {
|
|
119
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
120
|
-
for (const entry of entries) {
|
|
121
|
-
if (entry.isDirectory()) {
|
|
122
|
-
scanDir(path.join(dir, entry.name), maxDepth - 1);
|
|
123
|
-
}
|
|
124
|
-
}
|
|
125
|
-
}
|
|
126
|
-
catch { }
|
|
127
|
-
}
|
|
128
|
-
if (!sharedDir && (fs.existsSync(path.join(scopeDir, "pnpm-workspace.yaml")) || fs.existsSync(path.join(scopeDir, "nx.json")))) {
|
|
129
|
-
scanDir(scopeDir, 6);
|
|
130
|
-
}
|
|
131
|
-
else {
|
|
132
|
-
const candidates = [
|
|
133
|
-
path.join(scopeDir, entityType),
|
|
134
|
-
path.join(scopeDir, "support", entityType),
|
|
135
|
-
path.join(scopeDir, "support", "testing", entityType),
|
|
136
|
-
];
|
|
137
|
-
if (sharedDir) {
|
|
138
|
-
candidates.push(path.join(sharedDir, entityType));
|
|
139
|
-
candidates.push(path.join(sharedDir, "support", entityType));
|
|
140
|
-
candidates.push(path.join(sharedDir, "support", "testing", entityType));
|
|
141
|
-
}
|
|
142
|
-
for (const c of candidates) {
|
|
143
|
-
if (fs.existsSync(c) && !dirs.includes(c))
|
|
144
|
-
dirs.push(c);
|
|
145
|
-
}
|
|
146
|
-
}
|
|
147
|
-
return dirs;
|
|
148
|
-
}
|
|
149
|
-
static generateDeclarationsForScope(cwd, outputTypesDir, scopeDir, sharedDir) {
|
|
150
|
-
const poDirs = this.getPageObjectsDirsForScope(scopeDir, sharedDir);
|
|
151
|
-
const actionsDirs = this.getEntityDirsForScope("actions", scopeDir, sharedDir);
|
|
152
|
-
const stepsDirs = this.getEntityDirsForScope("steps", scopeDir, sharedDir);
|
|
153
|
-
const platforms = ["web", "android", "ios", "mobile", "common"];
|
|
154
|
-
const results = {};
|
|
155
|
-
for (const platform of platforms) {
|
|
156
|
-
const hierarchy = PLATFORM_HIERARCHY[platform];
|
|
157
|
-
let content = `// Auto-generated ambient declarations for TestSpectra [${platform.toUpperCase()}]\n`;
|
|
158
|
-
content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
|
|
159
|
-
content += `/// <reference types="@wdio/globals/types" />\n`;
|
|
160
|
-
content += `/// <reference types="@wdio/mocha-framework" />\n`;
|
|
161
|
-
content += `/// <reference types="@testspectra/matchers" />\n`;
|
|
162
|
-
content += `/// <reference path="./fixtures.d.ts" />\n\n`;
|
|
163
|
-
// 1. Page Objects Global Declarations
|
|
164
|
-
const declaredPOMs = new Set();
|
|
165
|
-
for (const poDir of poDirs) {
|
|
166
|
-
if (!fs.existsSync(poDir))
|
|
167
|
-
continue;
|
|
168
|
-
const poEntries = fs.readdirSync(poDir, { withFileTypes: true });
|
|
169
|
-
for (const entry of poEntries) {
|
|
170
|
-
if (entry.name.startsWith("."))
|
|
171
|
-
continue;
|
|
172
|
-
if (entry.isDirectory()) {
|
|
173
|
-
const className = entry.name;
|
|
174
|
-
if (declaredPOMs.has(className))
|
|
175
|
-
continue;
|
|
176
|
-
const classFolder = path.join(poDir, className);
|
|
177
|
-
let matchedFile = null;
|
|
178
|
-
for (const stem of hierarchy) {
|
|
179
|
-
const candidates = [
|
|
180
|
-
path.join(classFolder, `${stem}.ts`),
|
|
181
|
-
path.join(classFolder, `${stem}.page.ts`),
|
|
182
|
-
path.join(classFolder, `${stem}.po.ts`),
|
|
183
|
-
path.join(classFolder, "index.ts"),
|
|
184
|
-
path.join(classFolder, "common.ts"),
|
|
185
|
-
];
|
|
186
|
-
for (const cand of candidates) {
|
|
187
|
-
if (fs.existsSync(cand)) {
|
|
188
|
-
matchedFile = path.relative(outputTypesDir, cand).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
189
|
-
if (!matchedFile.startsWith("."))
|
|
190
|
-
matchedFile = `./${matchedFile}`;
|
|
191
|
-
break;
|
|
192
|
-
}
|
|
193
|
-
}
|
|
194
|
-
if (matchedFile)
|
|
195
|
-
break;
|
|
196
|
-
}
|
|
197
|
-
if (matchedFile) {
|
|
198
|
-
content += `declare const ${className}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
199
|
-
declaredPOMs.add(className);
|
|
200
|
-
}
|
|
201
|
-
}
|
|
202
|
-
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
203
|
-
const parts = entry.name.split(".");
|
|
204
|
-
const className = parts[0];
|
|
205
|
-
if (declaredPOMs.has(className))
|
|
206
|
-
continue;
|
|
207
|
-
let isMatch = false;
|
|
208
|
-
if (parts.length === 2) {
|
|
209
|
-
// E.g. ProfilePage.ts (applies to all platforms)
|
|
210
|
-
isMatch = true;
|
|
211
|
-
}
|
|
212
|
-
else if (parts.length > 2) {
|
|
213
|
-
// E.g. ProfilePage.web.ts or ProfilePage.android.ts
|
|
214
|
-
const stem = parts[1];
|
|
215
|
-
isMatch = hierarchy.includes(stem);
|
|
216
|
-
}
|
|
217
|
-
if (isMatch) {
|
|
218
|
-
let rel = path.relative(outputTypesDir, path.join(poDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
219
|
-
if (!rel.startsWith("."))
|
|
220
|
-
rel = `./${rel}`;
|
|
221
|
-
content += `declare const ${className}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
222
|
-
declaredPOMs.add(className);
|
|
223
|
-
}
|
|
224
|
-
}
|
|
225
|
-
}
|
|
226
|
-
}
|
|
227
|
-
// 2. Built-in Spectra Static Methods & Custom Actions
|
|
228
|
-
content += `interface TestSpectraCustomActions {\n`;
|
|
229
|
-
const declaredActions = new Set();
|
|
230
|
-
for (const actionsDir of actionsDirs) {
|
|
231
|
-
if (!fs.existsSync(actionsDir))
|
|
232
|
-
continue;
|
|
233
|
-
const actionEntries = fs.readdirSync(actionsDir, { withFileTypes: true });
|
|
234
|
-
for (const entry of actionEntries) {
|
|
235
|
-
if (entry.name.startsWith("."))
|
|
236
|
-
continue;
|
|
237
|
-
if (entry.isDirectory()) {
|
|
238
|
-
const actionName = entry.name;
|
|
239
|
-
if (declaredActions.has(actionName))
|
|
240
|
-
continue;
|
|
241
|
-
const actionFolder = path.join(actionsDir, actionName);
|
|
242
|
-
let matchedFile = null;
|
|
243
|
-
for (const stem of hierarchy) {
|
|
244
|
-
const cand1 = path.join(actionFolder, `${stem}.action.ts`);
|
|
245
|
-
const cand2 = path.join(actionFolder, `${stem}.ts`);
|
|
246
|
-
if (fs.existsSync(cand1)) {
|
|
247
|
-
matchedFile = path.relative(outputTypesDir, cand1).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
248
|
-
break;
|
|
249
|
-
}
|
|
250
|
-
else if (fs.existsSync(cand2)) {
|
|
251
|
-
matchedFile = path.relative(outputTypesDir, cand2).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
252
|
-
break;
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
if (matchedFile) {
|
|
256
|
-
if (!matchedFile.startsWith("."))
|
|
257
|
-
matchedFile = `./${matchedFile}`;
|
|
258
|
-
const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
259
|
-
content += ` "${actionName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
260
|
-
if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
261
|
-
content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
262
|
-
}
|
|
263
|
-
declaredActions.add(actionName);
|
|
264
|
-
}
|
|
265
|
-
}
|
|
266
|
-
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
267
|
-
const parts = entry.name.split(".");
|
|
268
|
-
const stem = parts.length > 2 ? parts[parts.length - 2] : parts[0];
|
|
269
|
-
const actionName = parts[0];
|
|
270
|
-
if (hierarchy.includes(stem) || hierarchy.includes(parts[1])) {
|
|
271
|
-
if (declaredActions.has(actionName))
|
|
272
|
-
continue;
|
|
273
|
-
let rel = path.relative(outputTypesDir, path.join(actionsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
274
|
-
if (!rel.startsWith("."))
|
|
275
|
-
rel = `./${rel}`;
|
|
276
|
-
const camel = actionName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
277
|
-
content += ` "${actionName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
278
|
-
if (camel !== actionName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
279
|
-
content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
280
|
-
}
|
|
281
|
-
declaredActions.add(actionName);
|
|
282
|
-
}
|
|
283
|
-
}
|
|
284
|
-
}
|
|
285
|
-
}
|
|
286
|
-
content += `}\n`;
|
|
287
|
-
content += `type TestSpectraActions = import('@testspectra/matchers').SpectraStatic & TestSpectraCustomActions;\n`;
|
|
288
|
-
content += `declare const Spectra: TestSpectraActions;\n\n`;
|
|
289
|
-
// 3. Built-in Steps Declaration
|
|
290
|
-
content += `interface TestSpectraSteps {\n`;
|
|
291
|
-
const declaredSteps = new Set();
|
|
292
|
-
for (const stepsDir of stepsDirs) {
|
|
293
|
-
if (!fs.existsSync(stepsDir))
|
|
294
|
-
continue;
|
|
295
|
-
const stepEntries = fs.readdirSync(stepsDir, { withFileTypes: true });
|
|
296
|
-
for (const entry of stepEntries) {
|
|
297
|
-
if (entry.name.startsWith("."))
|
|
298
|
-
continue;
|
|
299
|
-
if (entry.isDirectory()) {
|
|
300
|
-
const stepName = entry.name;
|
|
301
|
-
if (declaredSteps.has(stepName))
|
|
302
|
-
continue;
|
|
303
|
-
const stepFolder = path.join(stepsDir, stepName);
|
|
304
|
-
let matchedFile = null;
|
|
305
|
-
for (const stem of hierarchy) {
|
|
306
|
-
const cand1 = path.join(stepFolder, `${stem}.step.ts`);
|
|
307
|
-
const cand2 = path.join(stepFolder, `${stem}.ts`);
|
|
308
|
-
if (fs.existsSync(cand1)) {
|
|
309
|
-
matchedFile = path.relative(outputTypesDir, cand1).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
310
|
-
break;
|
|
311
|
-
}
|
|
312
|
-
else if (fs.existsSync(cand2)) {
|
|
313
|
-
matchedFile = path.relative(outputTypesDir, cand2).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
314
|
-
break;
|
|
315
|
-
}
|
|
316
|
-
}
|
|
317
|
-
if (matchedFile) {
|
|
318
|
-
if (!matchedFile.startsWith("."))
|
|
319
|
-
matchedFile = `./${matchedFile}`;
|
|
320
|
-
const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
321
|
-
content += ` "${stepName}": (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
322
|
-
if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
323
|
-
content += ` ${camel}: (typeof import('${matchedFile}') extends { default: infer T } ? T : typeof import('${matchedFile}'));\n`;
|
|
324
|
-
}
|
|
325
|
-
declaredSteps.add(stepName);
|
|
326
|
-
}
|
|
327
|
-
}
|
|
328
|
-
else if (entry.isFile() && entry.name.endsWith(".ts")) {
|
|
329
|
-
const parts = entry.name.split(".");
|
|
330
|
-
const stem = parts.length > 2 ? parts[parts.length - 2] : parts[0];
|
|
331
|
-
const stepName = parts[0];
|
|
332
|
-
if (hierarchy.includes(stem) || hierarchy.includes(parts[1])) {
|
|
333
|
-
if (declaredSteps.has(stepName))
|
|
334
|
-
continue;
|
|
335
|
-
let rel = path.relative(outputTypesDir, path.join(stepsDir, entry.name)).replace(/\\/g, "/").replace(/\.ts$/, ".js");
|
|
336
|
-
if (!rel.startsWith("."))
|
|
337
|
-
rel = `./${rel}`;
|
|
338
|
-
const camel = stepName.replace(/[-_]+([a-zA-Z0-9])/g, (_, char) => char.toUpperCase());
|
|
339
|
-
content += ` "${stepName}": (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
340
|
-
if (camel !== stepName && /^[a-zA-Z_$][a-zA-Z0-9_$]*$/.test(camel)) {
|
|
341
|
-
content += ` ${camel}: (typeof import('${rel}') extends { default: infer T } ? T : typeof import('${rel}'));\n`;
|
|
342
|
-
}
|
|
343
|
-
declaredSteps.add(stepName);
|
|
344
|
-
}
|
|
345
|
-
}
|
|
346
|
-
}
|
|
347
|
-
}
|
|
348
|
-
content += `}\n`;
|
|
349
|
-
content += `declare const Step: TestSpectraSteps;\n\n`;
|
|
350
|
-
results[platform] = content;
|
|
351
|
-
}
|
|
352
|
-
return results;
|
|
353
|
-
}
|
|
354
|
-
static generateFixturesDeclarationForScope(cwd, outputTypesDir, scopeDir, sharedDir) {
|
|
355
|
-
const fixturesDirs = this.getEntityDirsForScope("fixtures", scopeDir, sharedDir);
|
|
356
|
-
let content = `// Auto-generated ambient fixture declarations for TestSpectra\n`;
|
|
357
|
-
content += `// Managed automatically by TestSpectra Language Service Plugin.\n\n`;
|
|
358
|
-
content += `interface TestSpectraFixtures {\n`;
|
|
359
|
-
const declaredFixtures = new Set();
|
|
360
|
-
function scanFixtureDir(dir) {
|
|
361
|
-
if (!fs.existsSync(dir))
|
|
362
|
-
return;
|
|
363
|
-
try {
|
|
364
|
-
const entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
365
|
-
for (const entry of entries) {
|
|
366
|
-
if (entry.name.startsWith("."))
|
|
367
|
-
continue;
|
|
368
|
-
if (entry.isDirectory()) {
|
|
369
|
-
scanFixtureDir(path.join(dir, entry.name));
|
|
370
|
-
}
|
|
371
|
-
else if (entry.isFile()) {
|
|
372
|
-
const parsed = path.parse(entry.name);
|
|
373
|
-
const varName = parsed.name.replace(/[^a-zA-Z0-9_$]/g, "_");
|
|
374
|
-
if (declaredFixtures.has(varName))
|
|
375
|
-
continue;
|
|
376
|
-
if (entry.name.endsWith(".json")) {
|
|
377
|
-
let rel = path.relative(outputTypesDir, path.join(dir, entry.name)).replace(/\\/g, "/");
|
|
378
|
-
if (!rel.startsWith("."))
|
|
379
|
-
rel = `./${rel}`;
|
|
380
|
-
content += ` readonly ${varName}: typeof import('${rel}');\n`;
|
|
381
|
-
}
|
|
382
|
-
else {
|
|
383
|
-
content += ` readonly ${varName}: string;\n`;
|
|
384
|
-
}
|
|
385
|
-
declaredFixtures.add(varName);
|
|
386
|
-
}
|
|
387
|
-
}
|
|
388
|
-
}
|
|
389
|
-
catch { }
|
|
390
|
-
}
|
|
391
|
-
for (const fixturesDir of fixturesDirs) {
|
|
392
|
-
scanFixtureDir(fixturesDir);
|
|
393
|
-
}
|
|
394
|
-
content += `}\n`;
|
|
395
|
-
content += `declare const Fixture: TestSpectraFixtures;\n`;
|
|
396
|
-
return content;
|
|
397
|
-
}
|
|
398
|
-
static writeDeclarationFiles(cwd) {
|
|
399
|
-
const rootTypesDir = path.join(cwd, ".testspectra", "types");
|
|
400
|
-
if (!fs.existsSync(rootTypesDir)) {
|
|
401
|
-
fs.mkdirSync(rootTypesDir, { recursive: true });
|
|
402
|
-
}
|
|
403
|
-
const scopes = this.getEntityScopes(cwd);
|
|
404
|
-
const sharedScope = scopes.find((s) => s.isShared);
|
|
405
|
-
// 1. Generate Global / Standalone Root Types (or fallback)
|
|
406
|
-
const rootFixtures = this.generateFixturesDeclarationForScope(cwd, rootTypesDir, cwd);
|
|
407
|
-
fs.writeFileSync(path.join(rootTypesDir, "fixtures.d.ts"), rootFixtures, "utf-8");
|
|
408
|
-
const rootDeclarations = this.generateDeclarationsForScope(cwd, rootTypesDir, cwd);
|
|
409
|
-
for (const [platform, content] of Object.entries(rootDeclarations)) {
|
|
410
|
-
fs.writeFileSync(path.join(rootTypesDir, `${platform}.d.ts`), content, "utf-8");
|
|
411
|
-
}
|
|
412
|
-
// 2. Generate Scoped Types for Shared Library: .testspectra/types/shared/
|
|
413
|
-
if (sharedScope) {
|
|
414
|
-
const sharedTypesDir = path.join(rootTypesDir, "shared");
|
|
415
|
-
if (!fs.existsSync(sharedTypesDir))
|
|
416
|
-
fs.mkdirSync(sharedTypesDir, { recursive: true });
|
|
417
|
-
const sharedFixtures = this.generateFixturesDeclarationForScope(cwd, sharedTypesDir, sharedScope.dir);
|
|
418
|
-
fs.writeFileSync(path.join(sharedTypesDir, "fixtures.d.ts"), sharedFixtures, "utf-8");
|
|
419
|
-
// Shared scope has NO feature dependencies (only shared itself)
|
|
420
|
-
const sharedDeclarations = this.generateDeclarationsForScope(cwd, sharedTypesDir, sharedScope.dir);
|
|
421
|
-
for (const [platform, content] of Object.entries(sharedDeclarations)) {
|
|
422
|
-
fs.writeFileSync(path.join(sharedTypesDir, `${platform}.d.ts`), content, "utf-8");
|
|
423
|
-
}
|
|
424
|
-
}
|
|
425
|
-
// 3. Generate Scoped Types for Feature Modules: .testspectra/types/features/<featureName>/
|
|
426
|
-
const featureScopes = scopes.filter((s) => !s.isShared);
|
|
427
|
-
for (const feature of featureScopes) {
|
|
428
|
-
const featureTypesDir = path.join(rootTypesDir, "features", feature.name);
|
|
429
|
-
if (!fs.existsSync(featureTypesDir))
|
|
430
|
-
fs.mkdirSync(featureTypesDir, { recursive: true });
|
|
431
|
-
const featureFixtures = this.generateFixturesDeclarationForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
|
|
432
|
-
fs.writeFileSync(path.join(featureTypesDir, "fixtures.d.ts"), featureFixtures, "utf-8");
|
|
433
|
-
// Feature scope gets (feature + shared) ONLY
|
|
434
|
-
const featureDeclarations = this.generateDeclarationsForScope(cwd, featureTypesDir, feature.dir, sharedScope?.dir);
|
|
435
|
-
for (const [platform, content] of Object.entries(featureDeclarations)) {
|
|
436
|
-
fs.writeFileSync(path.join(featureTypesDir, `${platform}.d.ts`), content, "utf-8");
|
|
437
|
-
}
|
|
438
|
-
}
|
|
439
|
-
// Universal ambient index
|
|
440
|
-
const universalPath = path.join(rootTypesDir, "index.d.ts");
|
|
441
|
-
fs.writeFileSync(universalPath, `/// <reference path="./common.d.ts" />\n`, "utf-8");
|
|
442
|
-
}
|
|
443
|
-
}
|
|
1
|
+
export * from '../generator/type-generator.js';
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { DemoApiServer } from '../api-server.js';
|
|
3
|
+
describe('DemoApiServer self-hosted REST API (cli/src/utils/api-server.ts)', () => {
|
|
4
|
+
let server;
|
|
5
|
+
let port;
|
|
6
|
+
const base = () => `http://127.0.0.1:${port}`;
|
|
7
|
+
beforeAll(async () => {
|
|
8
|
+
server = new DemoApiServer(0);
|
|
9
|
+
const info = await server.start();
|
|
10
|
+
port = info.port;
|
|
11
|
+
});
|
|
12
|
+
afterAll(async () => {
|
|
13
|
+
await server?.stop();
|
|
14
|
+
});
|
|
15
|
+
it('GET /api/posts returns the self-hosted post list', async () => {
|
|
16
|
+
const res = await fetch(`${base()}/api/posts`);
|
|
17
|
+
expect(res.status).toBe(200);
|
|
18
|
+
const data = await res.json();
|
|
19
|
+
expect(Array.isArray(data)).toBe(true);
|
|
20
|
+
expect(data.length).toBeGreaterThan(0);
|
|
21
|
+
expect(data[0]).toHaveProperty('id');
|
|
22
|
+
expect(data[0]).toHaveProperty('title');
|
|
23
|
+
});
|
|
24
|
+
it('GET /api/users returns a user list with name/email', async () => {
|
|
25
|
+
const res = await fetch(`${base()}/api/users`);
|
|
26
|
+
expect(res.status).toBe(200);
|
|
27
|
+
const data = await res.json();
|
|
28
|
+
expect(Array.isArray(data)).toBe(true);
|
|
29
|
+
expect(data[0]).toHaveProperty('name');
|
|
30
|
+
expect(data[0]).toHaveProperty('email');
|
|
31
|
+
});
|
|
32
|
+
it('GET /api/todos/1 returns a single todo', async () => {
|
|
33
|
+
const res = await fetch(`${base()}/api/todos/1`);
|
|
34
|
+
expect(res.status).toBe(200);
|
|
35
|
+
const data = await res.json();
|
|
36
|
+
expect(data).toHaveProperty('id', 1);
|
|
37
|
+
expect(data).toHaveProperty('completed');
|
|
38
|
+
});
|
|
39
|
+
it('POST /api/posts creates a post with a numeric id (201)', async () => {
|
|
40
|
+
const res = await fetch(`${base()}/api/posts`, {
|
|
41
|
+
method: 'POST',
|
|
42
|
+
headers: { 'Content-Type': 'application/json; charset=UTF-8' },
|
|
43
|
+
body: JSON.stringify({ title: 'TestSpectra seeded post', body: 'Hook seeding', userId: 1 }),
|
|
44
|
+
});
|
|
45
|
+
expect(res.status).toBe(201);
|
|
46
|
+
const data = await res.json();
|
|
47
|
+
expect(typeof data.id).toBe('number');
|
|
48
|
+
expect(data.title).toBe('TestSpectra seeded post');
|
|
49
|
+
});
|
|
50
|
+
it('DELETE /api/posts/:id cleans up a seeded post (200)', async () => {
|
|
51
|
+
const res = await fetch(`${base()}/api/posts/123`, { method: 'DELETE' });
|
|
52
|
+
expect(res.status).toBe(200);
|
|
53
|
+
});
|
|
54
|
+
it('returns CORS headers on API responses', async () => {
|
|
55
|
+
const res = await fetch(`${base()}/api/posts`);
|
|
56
|
+
expect(res.headers.get('access-control-allow-origin')).toBe('*');
|
|
57
|
+
});
|
|
58
|
+
it('returns 404 for unknown API routes', async () => {
|
|
59
|
+
const res = await fetch(`${base()}/api/does-not-exist`);
|
|
60
|
+
expect(res.status).toBe(404);
|
|
61
|
+
});
|
|
62
|
+
});
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export {};
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import { describe, it, expect, beforeAll, afterAll } from 'vitest';
|
|
2
|
+
import { DemoServer } from '../demo-server.js';
|
|
3
|
+
describe('DemoServer static web app (cli/src/utils/demo-server.ts)', () => {
|
|
4
|
+
let server;
|
|
5
|
+
let port;
|
|
6
|
+
const demoDistAvailable = DemoServer.resolveDemoDist() !== null;
|
|
7
|
+
const base = () => `http://127.0.0.1:${port}`;
|
|
8
|
+
beforeAll(async () => {
|
|
9
|
+
if (!demoDistAvailable)
|
|
10
|
+
return;
|
|
11
|
+
server = new DemoServer(0);
|
|
12
|
+
const info = await server.start();
|
|
13
|
+
port = info.port;
|
|
14
|
+
});
|
|
15
|
+
afterAll(async () => {
|
|
16
|
+
await server?.stop();
|
|
17
|
+
});
|
|
18
|
+
it.skipIf(!demoDistAvailable)('serves the demo web app index', async () => {
|
|
19
|
+
const res = await fetch(`${base()}/`);
|
|
20
|
+
expect(res.status).toBe(200);
|
|
21
|
+
expect(await res.text()).toContain('<div id="app"></div>');
|
|
22
|
+
});
|
|
23
|
+
it.skipIf(!demoDistAvailable)('falls back to index.html for SPA routes', async () => {
|
|
24
|
+
const res = await fetch(`${base()}/some/spa/route`);
|
|
25
|
+
expect(res.status).toBe(200);
|
|
26
|
+
expect(await res.text()).toContain('<div id="app"></div>');
|
|
27
|
+
});
|
|
28
|
+
it.skipIf(!demoDistAvailable)('does not serve /api/* (that is the separate DemoApiServer)', async () => {
|
|
29
|
+
const res = await fetch(`${base()}/api/posts`);
|
|
30
|
+
// /api/posts is not a static file, so the SPA fallback serves index.html (200) — never JSON.
|
|
31
|
+
const text = await res.text();
|
|
32
|
+
expect(text).toContain('<div id="app"></div>');
|
|
33
|
+
expect(() => JSON.parse(text)).toThrow();
|
|
34
|
+
});
|
|
35
|
+
});
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
/** Port the self-hosted demo API server listens on (separate from the demo web app's 5173). */
|
|
2
|
+
export declare const DEMO_API_PORT = 5200;
|
|
3
|
+
/**
|
|
4
|
+
* Standalone self-hosted REST API server for the demo apps, kept separate from `DemoServer` (the
|
|
5
|
+
* static web app) so the API can run independently and either server can be swapped for a real
|
|
6
|
+
* backend in a project. Mirrors the small subset of jsonplaceholder.typicode.com shapes the demo's
|
|
7
|
+
* API sections exercise.
|
|
8
|
+
*/
|
|
9
|
+
export declare class DemoApiServer {
|
|
10
|
+
private server;
|
|
11
|
+
private port;
|
|
12
|
+
constructor(port?: number);
|
|
13
|
+
start(): Promise<{
|
|
14
|
+
url: string;
|
|
15
|
+
port: number;
|
|
16
|
+
}>;
|
|
17
|
+
private handleApi;
|
|
18
|
+
stop(): Promise<void>;
|
|
19
|
+
}
|