Compare commits
	
		
			84 Commits
		
	
	
		
			v0.26.2
			...
			exports-na
		
	
	| Author | SHA1 | Date | |
|---|---|---|---|
| 739a5c5132 | |||
| 97b9529c81 | |||
| 8e5fc02941 | |||
| 909690f3c7 | |||
| 14ba66378d | |||
| b2e895e508 | |||
| 05f4f34269 | |||
| fbb7b08b62 | |||
| 16c7a2457a | |||
| c429bc6ed7 | |||
| a0493cb332 | |||
| b798f7da03 | |||
| c5d051855f | |||
| 0b24216dc5 | |||
| 2d3841bf61 | |||
| 51a71a180e | |||
| 1ec25dfe96 | |||
| 8de29dd461 | |||
| b11040c23c | |||
| 2bc4f076cb | |||
| 9e1cf90c81 | |||
| 062fae1e54 | |||
| d7660e221c | |||
| 938e27adac | |||
| 17b9af2416 | |||
| 64f0f5b773 | |||
| f452f9bf00 | |||
| 97705234c6 | |||
| 30dfc167d3 | |||
| d8105627c0 | |||
| 6b7fac3642 | |||
| 35805916aa | |||
| 4a4400e979 | |||
| efd1f288b9 | |||
| 0337ab9cff | |||
| f0dda692f6 | |||
| 2ce0c59d08 | |||
| 393b43d485 | |||
| 4fbcde8773 | |||
| 12d444fa69 | |||
| 683b4488af | |||
| e1c1e07046 | |||
| 984420c155 | |||
| 7bad60dfa3 | |||
| aaca88220c | |||
| 360384e8c8 | |||
| ab2ad1313f | |||
| 897205acc2 | |||
| 862ca1124e | |||
| d9981d9d7b | |||
| 8df0581831 | |||
| 54e6358df1 | |||
| daf20a978d | |||
| 8e64798dda | |||
| a1ceb4fa47 | |||
| 2db8d13051 | |||
| aceb8052e2 | |||
| 62fae1e93b | |||
| 2abfbb9788 | |||
| ad1cd56891 | |||
| 26951364cf | |||
| 26e995dc3f | |||
| a8b816a3e2 | |||
| 43bec115c0 | |||
| 0c6c646fe7 | |||
| 0d52851da2 | |||
| 6b105897f7 | |||
| 9ff51de301 | |||
| c161f578fd | |||
| 4804eedf3e | |||
| 99db31a6a4 | |||
| 90b57ec202 | |||
| 3f86f99f5e | |||
| 83e2b093a6 | |||
| 58f7e0086d | |||
| c147b3bfa2 | |||
| 7103ded32a | |||
| 81279aa4e8 | |||
| 550c8ae165 | |||
| 05610bb0f3 | |||
| 4a62862ca0 | |||
| a4783d4951 | |||
| 30cfac06b8 | |||
| c5509dabb1 | 
| @ -4,9 +4,9 @@ set -euo pipefail | |||||||
| if [[ ! -f "test-results/.last-run.json" ]]; then | if [[ ! -f "test-results/.last-run.json" ]]; then | ||||||
|     # if no last run artifact, than run plawright normally |     # if no last run artifact, than run plawright normally | ||||||
|     echo "run playwright normally" |     echo "run playwright normally" | ||||||
|     if [[ "$3" == "ubuntu-latest" ]]; then |     if [[ "$3" == ubuntu-latest* ]]; then | ||||||
|         yarn test:playwright:browser:chrome:ubuntu -- --shard=$1/$2 || true |         yarn test:playwright:browser:chrome:ubuntu -- --shard=$1/$2 || true | ||||||
|     elif [[ "$3" == "windows-latest" ]]; then |     elif [[ "$3" == windows-latest* ]]; then | ||||||
|         yarn test:playwright:browser:chrome:windows -- --shard=$1/$2 || true |         yarn test:playwright:browser:chrome:windows -- --shard=$1/$2 || true | ||||||
|     else |     else | ||||||
|         echo "Do not run playwright. Unable to detect os runtime." |         echo "Do not run playwright. Unable to detect os runtime." | ||||||
| @ -26,9 +26,9 @@ while [[ $retry -le $max_retrys ]]; do | |||||||
|         if [[ $failed_tests -gt 0 ]]; then |         if [[ $failed_tests -gt 0 ]]; then | ||||||
|             echo "retried=true" >>$GITHUB_OUTPUT |             echo "retried=true" >>$GITHUB_OUTPUT | ||||||
|             echo "run playwright with last failed tests and retry $retry" |             echo "run playwright with last failed tests and retry $retry" | ||||||
|             if [[ "$3" == "ubuntu-latest" ]]; then |             if [[ "$3" == ubuntu-latest* ]]; then | ||||||
|                 yarn test:playwright:browser:chrome:ubuntu -- --last-failed || true |                 yarn test:playwright:browser:chrome:ubuntu -- --last-failed || true | ||||||
|             elif [[ "$3" == "windows-latest" ]]; then |             elif [[ "$3" == windows-latest* ]]; then | ||||||
|                 yarn test:playwright:browser:chrome:windows -- --last-failed || true |                 yarn test:playwright:browser:chrome:windows -- --last-failed || true | ||||||
|             else |             else | ||||||
|                 echo "Do not run playwright. Unable to detect os runtime." |                 echo "Do not run playwright. Unable to detect os runtime." | ||||||
|  | |||||||
							
								
								
									
										12
									
								
								.github/ci-cd-scripts/playwright-electron.sh
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -4,11 +4,11 @@ set -euo pipefail | |||||||
| if [[ ! -f "test-results/.last-run.json" ]]; then | if [[ ! -f "test-results/.last-run.json" ]]; then | ||||||
|     # if no last run artifact, than run plawright normally |     # if no last run artifact, than run plawright normally | ||||||
|     echo "run playwright normally" |     echo "run playwright normally" | ||||||
|         if [[ "$1" == "ubuntu-latest" ]]; then |         if [[ "$1" == ubuntu-latest* ]]; then | ||||||
|             xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true |             xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu || true | ||||||
|         elif [[ "$1" == "windows-latest" ]]; then |         elif [[ "$1" == windows-latest* ]]; then | ||||||
|             yarn test:playwright:electron:windows || true |             yarn test:playwright:electron:windows || true | ||||||
|         elif [[ "$1" == "macos-14" ]]; then |         elif [[ "$1" == macos-14* ]]; then | ||||||
|             yarn test:playwright:electron:macos || true |             yarn test:playwright:electron:macos || true | ||||||
|         else |         else | ||||||
|             echo "Do not run playwright. Unable to detect os runtime." |             echo "Do not run playwright. Unable to detect os runtime." | ||||||
| @ -28,11 +28,11 @@ while [[ $retry -le $max_retrys ]]; do | |||||||
|         if [[ $failed_tests -gt 0 ]]; then |         if [[ $failed_tests -gt 0 ]]; then | ||||||
|             echo "retried=true" >>$GITHUB_OUTPUT |             echo "retried=true" >>$GITHUB_OUTPUT | ||||||
|             echo "run playwright with last failed tests and retry $retry" |             echo "run playwright with last failed tests and retry $retry" | ||||||
|             if [[ "$1" == "ubuntu-latest" ]]; then |             if [[ "$1" == ubuntu-latest* ]]; then | ||||||
|                 xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true |                 xvfb-run --auto-servernum --server-args="-screen 0 1280x960x24" -- yarn test:playwright:electron:ubuntu -- --last-failed || true | ||||||
|             elif [[ "$1" == "windows-latest" ]]; then |             elif [[ "$1" == windows-latest* ]]; then | ||||||
|                 yarn test:playwright:electron:windows -- --last-failed || true |                 yarn test:playwright:electron:windows -- --last-failed || true | ||||||
|             elif [[ "$1" == "macos-14" ]]; then |             elif [[ "$1" == macos-14* ]]; then | ||||||
|                 yarn test:playwright:electron:macos -- --last-failed || true |                 yarn test:playwright:electron:macos -- --last-failed || true | ||||||
|             else |             else | ||||||
|                 echo "Do not run playwright. Unable to detect os runtime." |                 echo "Do not run playwright. Unable to detect os runtime." | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								.github/dependabot.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -8,21 +8,21 @@ updates: | |||||||
|     - package-ecosystem: 'npm' # See documentation for possible values |     - package-ecosystem: 'npm' # See documentation for possible values | ||||||
|       directory: '/' # Location of package manifests |       directory: '/' # Location of package manifests | ||||||
|       schedule: |       schedule: | ||||||
|           interval: 'daily' |           interval: 'weekly' | ||||||
|       reviewers: |       reviewers: | ||||||
|           - franknoirot |           - franknoirot | ||||||
|           - irev-dev |           - irev-dev | ||||||
|     - package-ecosystem: 'github-actions' # See documentation for possible values |     - package-ecosystem: 'github-actions' # See documentation for possible values | ||||||
|       directory: '/' # Location of package manifests |       directory: '/' # Location of package manifests | ||||||
|       schedule: |       schedule: | ||||||
|           interval: 'daily' |           interval: 'weekly' | ||||||
|       reviewers: |       reviewers: | ||||||
|           - adamchalmers |           - adamchalmers | ||||||
|           - jessfraz |           - jessfraz | ||||||
|     - package-ecosystem: 'cargo' # See documentation for possible values |     - package-ecosystem: 'cargo' # See documentation for possible values | ||||||
|       directory: '/src/wasm-lib/' # Location of package manifests |       directory: '/src/wasm-lib/' # Location of package manifests | ||||||
|       schedule: |       schedule: | ||||||
|           interval: 'daily' |           interval: 'weekly' | ||||||
|       reviewers: |       reviewers: | ||||||
|           - adamchalmers |           - adamchalmers | ||||||
|           - jessfraz |           - jessfraz | ||||||
|  | |||||||
							
								
								
									
										55
									
								
								.github/workflows/build-test-publish-apps.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -85,7 +85,7 @@ jobs: | |||||||
|       - name: Prepare electron-builder.yml file for updater test |       - name: Prepare electron-builder.yml file for updater test | ||||||
|         if: ${{ env.CUT_RELEASE_PR == 'true' }} |         if: ${{ env.CUT_RELEASE_PR == 'true' }} | ||||||
|         run: | |         run: | | ||||||
|           yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test-release-notes"' electron-builder.yml |           yq -i '.publish[0].url = "https://dl.zoo.dev/releases/modeling-app/updater-test"' electron-builder.yml | ||||||
|  |  | ||||||
|       - uses: actions/upload-artifact@v3 |       - uses: actions/upload-artifact@v3 | ||||||
|         if: ${{ env.CUT_RELEASE_PR == 'true' }} |         if: ${{ env.CUT_RELEASE_PR == 'true' }} | ||||||
| @ -109,17 +109,8 @@ jobs: | |||||||
|             platform: linux |             platform: linux | ||||||
|     runs-on: ${{ matrix.os }} |     runs-on: ${{ matrix.os }} | ||||||
|     env: |     env: | ||||||
|       APPLE_ID: ${{ secrets.APPLE_ID }} |  | ||||||
|       APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} |  | ||||||
|       APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} |  | ||||||
|       APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} |  | ||||||
|       CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} |  | ||||||
|       CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} |  | ||||||
|       CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} |  | ||||||
|       CSC_FOR_PULL_REQUEST: true |  | ||||||
|       VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }} |       VERSION: ${{ github.event_name == 'schedule' && needs.prepare-files.outputs.version || format('v{0}', needs.prepare-files.outputs.version) }} | ||||||
|       VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} |       VERSION_NO_V: ${{ needs.prepare-files.outputs.version }} | ||||||
|       WINDOWS_CERTIFICATE_THUMBPRINT: F4C9A52FF7BC26EE5E054946F6B11DEEA94C748D |  | ||||||
|     steps: |     steps: | ||||||
|       - uses: actions/checkout@v4 |       - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
| @ -181,8 +172,26 @@ jobs: | |||||||
|           smksp_cert_sync.exe |           smksp_cert_sync.exe | ||||||
|         shell: cmd |         shell: cmd | ||||||
|  |  | ||||||
|       - name: Build the app |       - name: Build the app (debug) | ||||||
|         run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }} |         if: ${{ env.BUILD_RELEASE == 'false' }} | ||||||
|  |         # electron-builder doesn't have a concept of release vs debug, | ||||||
|  |         # this is just not doing any codesign or release yml generation | ||||||
|  |         run: yarn electron-builder --config | ||||||
|  |  | ||||||
|  |       - name: Build the app (release) | ||||||
|  |         if: ${{ env.BUILD_RELEASE == 'true' }} | ||||||
|  |         env: | ||||||
|  |           PUBLISH_FOR_PULL_REQUEST: true | ||||||
|  |           APPLE_ID: ${{ secrets.APPLE_ID }} | ||||||
|  |           APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | ||||||
|  |           APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | ||||||
|  |           APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | ||||||
|  |           CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} | ||||||
|  |           CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | ||||||
|  |           CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} | ||||||
|  |           CSC_FOR_PULL_REQUEST: true | ||||||
|  |           WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} | ||||||
|  |         run: yarn electron-builder --config --publish always | ||||||
|  |  | ||||||
|       - name: List artifacts in out/ |       - name: List artifacts in out/ | ||||||
|         run: ls -R out |         run: ls -R out | ||||||
| @ -226,7 +235,17 @@ jobs: | |||||||
|  |  | ||||||
|       - name: Build the app (updater-test) |       - name: Build the app (updater-test) | ||||||
|         if: ${{ env.CUT_RELEASE_PR == 'true' }} |         if: ${{ env.CUT_RELEASE_PR == 'true' }} | ||||||
|         run: yarn electron-builder --config ${{ env.BUILD_RELEASE && '--publish always' || '' }} |         env: | ||||||
|  |           APPLE_ID: ${{ secrets.APPLE_ID }} | ||||||
|  |           APPLE_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | ||||||
|  |           APPLE_APP_SPECIFIC_PASSWORD: ${{ secrets.APPLE_PASSWORD }} | ||||||
|  |           APPLE_TEAM_ID: ${{ secrets.APPLE_TEAM_ID }} | ||||||
|  |           CSC_LINK: ${{ secrets.APPLE_CERTIFICATE }} | ||||||
|  |           CSC_KEY_PASSWORD: ${{ secrets.APPLE_CERTIFICATE_PASSWORD }} | ||||||
|  |           CSC_KEYCHAIN: ${{ secrets.APPLE_SIGNING_IDENTITY }} | ||||||
|  |           CSC_FOR_PULL_REQUEST: true | ||||||
|  |           WINDOWS_CERTIFICATE_THUMBPRINT: ${{ secrets.WINDOWS_CERTIFICATE_THUMBPRINT }} | ||||||
|  |         run: yarn electron-builder --config --publish always | ||||||
|  |  | ||||||
|       - uses: actions/upload-artifact@v3 |       - uses: actions/upload-artifact@v3 | ||||||
|         if: ${{ env.CUT_RELEASE_PR == 'true' }} |         if: ${{ env.CUT_RELEASE_PR == 'true' }} | ||||||
| @ -342,17 +361,17 @@ jobs: | |||||||
|         run: "ls -R out" |         run: "ls -R out" | ||||||
|  |  | ||||||
|       - name: Authenticate to Google Cloud |       - name: Authenticate to Google Cloud | ||||||
|         uses: 'google-github-actions/auth@v2.1.6' |         uses: 'google-github-actions/auth@v2.1.7' | ||||||
|         with: |         with: | ||||||
|           credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}' |           credentials_json: '${{ secrets.GOOGLE_CLOUD_DL_SA }}' | ||||||
|  |  | ||||||
|       - name: Set up Google Cloud SDK |       - name: Set up Google Cloud SDK | ||||||
|         uses: google-github-actions/setup-gcloud@v2.1.0 |         uses: google-github-actions/setup-gcloud@v2.1.2 | ||||||
|         with: |         with: | ||||||
|           project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }} |           project_id: ${{ env.GOOGLE_CLOUD_PROJECT_ID }} | ||||||
|  |  | ||||||
|       - name: Upload release files to public bucket |       - name: Upload release files to public bucket | ||||||
|         uses: google-github-actions/upload-cloud-storage@v2.2.0 |         uses: google-github-actions/upload-cloud-storage@v2.2.1 | ||||||
|         with: |         with: | ||||||
|           path: out |           path: out | ||||||
|           glob: 'Zoo*' |           glob: 'Zoo*' | ||||||
| @ -360,7 +379,7 @@ jobs: | |||||||
|           destination: ${{ env.BUCKET_DIR }} |           destination: ${{ env.BUCKET_DIR }} | ||||||
|  |  | ||||||
|       - name: Upload update endpoint to public bucket |       - name: Upload update endpoint to public bucket | ||||||
|         uses: google-github-actions/upload-cloud-storage@v2.2.0 |         uses: google-github-actions/upload-cloud-storage@v2.2.1 | ||||||
|         with: |         with: | ||||||
|           path: out |           path: out | ||||||
|           glob: 'latest*' |           glob: 'latest*' | ||||||
| @ -368,7 +387,7 @@ jobs: | |||||||
|           destination: ${{ env.BUCKET_DIR }} |           destination: ${{ env.BUCKET_DIR }} | ||||||
|  |  | ||||||
|       - name: Upload download endpoint to public bucket |       - name: Upload download endpoint to public bucket | ||||||
|         uses: google-github-actions/upload-cloud-storage@v2.2.0 |         uses: google-github-actions/upload-cloud-storage@v2.2.1 | ||||||
|         with: |         with: | ||||||
|           path: last_download.json |           path: last_download.json | ||||||
|           destination: ${{ env.BUCKET_DIR }} |           destination: ${{ env.BUCKET_DIR }} | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								.github/workflows/cargo-check.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -37,4 +37,4 @@ jobs: | |||||||
|           # We specifically want to test the disable-println feature |           # We specifically want to test the disable-println feature | ||||||
|           # Since it is not enabled by default, we need to specify it |           # Since it is not enabled by default, we need to specify it | ||||||
|           # This is used in kcl-lsp |           # This is used in kcl-lsp | ||||||
|           cargo check --all --features disable-println --features pyo3 --features cli |           cargo check --workspace --features disable-println --features pyo3 --features cli | ||||||
|  | |||||||
							
								
								
									
										4
									
								
								.github/workflows/cargo-test.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -5,6 +5,8 @@ on: | |||||||
|     paths: |     paths: | ||||||
|       - 'src/wasm-lib/**.rs' |       - 'src/wasm-lib/**.rs' | ||||||
|       - 'src/wasm-lib/**.hbs' |       - 'src/wasm-lib/**.hbs' | ||||||
|  |       - 'src/wasm-lib/**.gen' | ||||||
|  |       - 'src/wasm-lib/**.snap' | ||||||
|       - '**/Cargo.toml' |       - '**/Cargo.toml' | ||||||
|       - '**/Cargo.lock' |       - '**/Cargo.lock' | ||||||
|       - '**/rust-toolchain.toml' |       - '**/rust-toolchain.toml' | ||||||
| @ -15,6 +17,8 @@ on: | |||||||
|     paths: |     paths: | ||||||
|       - 'src/wasm-lib/**.rs' |       - 'src/wasm-lib/**.rs' | ||||||
|       - 'src/wasm-lib/**.hbs' |       - 'src/wasm-lib/**.hbs' | ||||||
|  |       - 'src/wasm-lib/**.gen' | ||||||
|  |       - 'src/wasm-lib/**.snap' | ||||||
|       - '**/Cargo.toml' |       - '**/Cargo.toml' | ||||||
|       - '**/Cargo.lock' |       - '**/Cargo.lock' | ||||||
|       - '**/rust-toolchain.toml' |       - '**/rust-toolchain.toml' | ||||||
|  | |||||||
							
								
								
									
										6
									
								
								.github/workflows/e2e-tests.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -39,7 +39,7 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         os: [ubuntu-latest, windows-latest] |         os: [ubuntu-latest-8-cores, windows-latest-8-cores] | ||||||
|         shardIndex: [1, 2, 3, 4] |         shardIndex: [1, 2, 3, 4] | ||||||
|         shardTotal: [4] |         shardTotal: [4] | ||||||
|     runs-on: ${{ matrix.os }} |     runs-on: ${{ matrix.os }} | ||||||
| @ -227,7 +227,7 @@ jobs: | |||||||
|     strategy: |     strategy: | ||||||
|       fail-fast: false |       fail-fast: false | ||||||
|       matrix: |       matrix: | ||||||
|         os: [ubuntu-latest, windows-latest, macos-14] |         os: [ubuntu-latest-8-cores, windows-latest-8-cores, macos-14-large] | ||||||
|     timeout-minutes: 60 |     timeout-minutes: 60 | ||||||
|     runs-on: ${{ matrix.os }} |     runs-on: ${{ matrix.os }} | ||||||
|     needs: check-rust-changes |     needs: check-rust-changes | ||||||
| @ -287,7 +287,7 @@ jobs: | |||||||
|         brew install gnu-sed |         brew install gnu-sed | ||||||
|         echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH |         echo "/opt/homebrew/opt/gnu-sed/libexec/gnubin" >> $GITHUB_PATH | ||||||
|     - name: Install vector |     - name: Install vector | ||||||
|       if:  ${{ !startsWith(matrix.os, 'windows') }} |       if:  ${{ startsWith(matrix.os, 'ubuntu') }} | ||||||
|       shell: bash |       shell: bash | ||||||
|       run: | |       run: | | ||||||
|         curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh |         curl --proto '=https' --tlsv1.2 -sSfL https://sh.vector.dev > /tmp/vector.sh | ||||||
|  | |||||||
							
								
								
									
										25
									
								
								.github/workflows/static-analysis.yml
									
									
									
									
										vendored
									
									
								
							
							
						
						| @ -81,6 +81,31 @@ jobs: | |||||||
|       - name: Run codespell |       - name: Run codespell | ||||||
|         run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration. |         run: codespell --config .codespellrc # Edit this file to tweak the typo list and other configuration. | ||||||
|  |  | ||||||
|  |   yarn-unit-test-kcl-samples: | ||||||
|  |     runs-on: ubuntu-latest | ||||||
|  |     steps: | ||||||
|  |       - uses: actions/checkout@v4 | ||||||
|  |  | ||||||
|  |       - uses: actions/setup-node@v4 | ||||||
|  |         with: | ||||||
|  |           node-version-file: '.nvmrc' | ||||||
|  |           cache: 'yarn' | ||||||
|  |  | ||||||
|  |       - run: yarn install | ||||||
|  |       - run: yarn build:wasm | ||||||
|  |  | ||||||
|  |       - run: yarn simpleserver:bg | ||||||
|  |         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||||
|  |  | ||||||
|  |       - name: Install Chromium Browser | ||||||
|  |         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||||
|  |         run: yarn playwright install chromium --with-deps | ||||||
|  |  | ||||||
|  |       - name: run unit tests for kcl samples | ||||||
|  |         if: ${{ github.event_name != 'release' && github.event_name != 'schedule' }} | ||||||
|  |         run: yarn test:unit:kcl-samples | ||||||
|  |         env: | ||||||
|  |           VITE_KC_DEV_TOKEN: ${{ secrets.KITTYCAD_API_TOKEN_DEV }} | ||||||
|  |  | ||||||
|   yarn-unit-test: |   yarn-unit-test: | ||||||
|     runs-on: ubuntu-latest |     runs-on: ubuntu-latest | ||||||
|  | |||||||
							
								
								
									
										2
									
								
								Makefile
									
									
									
									
									
								
							
							
						
						| @ -19,7 +19,7 @@ $(XSTATE_TYPEGENS): $(TS_SRC) | |||||||
| 	yarn xstate typegen 'src/**/*.ts?(x)' | 	yarn xstate typegen 'src/**/*.ts?(x)' | ||||||
|  |  | ||||||
| public/wasm_lib_bg.wasm: $(WASM_LIB_FILES) | public/wasm_lib_bg.wasm: $(WASM_LIB_FILES) | ||||||
| 	yarn build:wasm-dev | 	yarn build:wasm | ||||||
|  |  | ||||||
| node_modules: package.json yarn.lock | node_modules: package.json yarn.lock | ||||||
| 	yarn install | 	yarn install | ||||||
|  | |||||||
| @ -110,7 +110,7 @@ Which commands from setup are one off vs need to be run every time? | |||||||
| The following will need to be run when checking out a new commit and guarantees the build is not stale: | The following will need to be run when checking out a new commit and guarantees the build is not stale: | ||||||
| ```bash | ```bash | ||||||
| yarn install | yarn install | ||||||
| yarn build:wasm-dev # or yarn build:wasm for slower but more production-like build | yarn build:wasm | ||||||
| yarn start # or yarn build:local && yarn serve for slower but more production-like build | yarn start # or yarn build:local && yarn serve for slower but more production-like build | ||||||
| ``` | ``` | ||||||
|  |  | ||||||
|  | |||||||
| @ -1,10 +1,10 @@ | |||||||
| --- | --- | ||||||
| title: "angleToMatchLengthX" | title: "angleToMatchLengthX" | ||||||
| excerpt: "Compute the angle (in degrees) in o" | excerpt: "Returns the angle to match the given length for x." | ||||||
| layout: manual | layout: manual | ||||||
| --- | --- | ||||||
|  |  | ||||||
| Compute the angle (in degrees) in o | Returns the angle to match the given length for x. | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ layout: manual | |||||||
| ## Table of Contents | ## Table of Contents | ||||||
|  |  | ||||||
| * [Types](kcl/types) | * [Types](kcl/types) | ||||||
|  | * [Modules](kcl/modules) | ||||||
| * [Known Issues](kcl/KNOWN-ISSUES) | * [Known Issues](kcl/KNOWN-ISSUES) | ||||||
| * [`abs`](kcl/abs) | * [`abs`](kcl/abs) | ||||||
| * [`acos`](kcl/acos) | * [`acos`](kcl/acos) | ||||||
| @ -74,17 +75,23 @@ layout: manual | |||||||
| * [`patternTransform`](kcl/patternTransform) | * [`patternTransform`](kcl/patternTransform) | ||||||
| * [`pi`](kcl/pi) | * [`pi`](kcl/pi) | ||||||
| * [`polar`](kcl/polar) | * [`polar`](kcl/polar) | ||||||
|  | * [`polygon`](kcl/polygon) | ||||||
| * [`pow`](kcl/pow) | * [`pow`](kcl/pow) | ||||||
| * [`profileStart`](kcl/profileStart) | * [`profileStart`](kcl/profileStart) | ||||||
| * [`profileStartX`](kcl/profileStartX) | * [`profileStartX`](kcl/profileStartX) | ||||||
| * [`profileStartY`](kcl/profileStartY) | * [`profileStartY`](kcl/profileStartY) | ||||||
|  | * [`push`](kcl/push) | ||||||
| * [`reduce`](kcl/reduce) | * [`reduce`](kcl/reduce) | ||||||
| * [`rem`](kcl/rem) | * [`rem`](kcl/rem) | ||||||
| * [`revolve`](kcl/revolve) | * [`revolve`](kcl/revolve) | ||||||
| * [`segAng`](kcl/segAng) | * [`segAng`](kcl/segAng) | ||||||
|  | * [`segEnd`](kcl/segEnd) | ||||||
| * [`segEndX`](kcl/segEndX) | * [`segEndX`](kcl/segEndX) | ||||||
| * [`segEndY`](kcl/segEndY) | * [`segEndY`](kcl/segEndY) | ||||||
| * [`segLen`](kcl/segLen) | * [`segLen`](kcl/segLen) | ||||||
|  | * [`segStart`](kcl/segStart) | ||||||
|  | * [`segStartX`](kcl/segStartX) | ||||||
|  | * [`segStartY`](kcl/segStartY) | ||||||
| * [`shell`](kcl/shell) | * [`shell`](kcl/shell) | ||||||
| * [`sin`](kcl/sin) | * [`sin`](kcl/sin) | ||||||
| * [`sqrt`](kcl/sqrt) | * [`sqrt`](kcl/sqrt) | ||||||
|  | |||||||
							
								
								
									
										59
									
								
								docs/kcl/modules.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,59 @@ | |||||||
|  | --- | ||||||
|  | title: "KCL Modules" | ||||||
|  | excerpt: "Documentation of modules for the KCL language for the Zoo Modeling App." | ||||||
|  | layout: manual | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | `KCL` allows splitting code up into multiple files.  Each file is somewhat | ||||||
|  | isolated from other files as a separate module. | ||||||
|  |  | ||||||
|  | When you define a function, you can use `export` before it to make it available | ||||||
|  | to other modules. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | // util.kcl | ||||||
|  | export fn increment = (x) => { | ||||||
|  |   return x + 1 | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Other files in the project can now import functions that have been exported. | ||||||
|  | This makes them available to use in another file. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | // main.kcl | ||||||
|  | import increment from "util.kcl" | ||||||
|  |  | ||||||
|  | answer = increment(41) | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Imported files _must_ be in the same project so that units are uniform across | ||||||
|  | modules. This means that it must be in the same directory. | ||||||
|  |  | ||||||
|  | Import statements must be at the top-level of a file. It is not allowed to have | ||||||
|  | an `import` statement inside a function or in the body of an if-else. | ||||||
|  |  | ||||||
|  | Multiple functions can be exported in a file. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | // util.kcl | ||||||
|  | export fn increment = (x) => { | ||||||
|  |   return x + 1 | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export fn decrement = (x) => { | ||||||
|  |   return x - 1 | ||||||
|  | } | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | When importing, you can import multiple functions at once. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | import increment, decrement from "util.kcl" | ||||||
|  | ``` | ||||||
|  |  | ||||||
|  | Imported symbols can be renamed for convenience or to avoid name collisions. | ||||||
|  |  | ||||||
|  | ``` | ||||||
|  | import increment as inc, decrement as dec from "util.kcl" | ||||||
|  | ``` | ||||||
							
								
								
									
										60
									
								
								docs/kcl/polygon.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										38
									
								
								docs/kcl/push.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										53
									
								
								docs/kcl/segEnd.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										56
									
								
								docs/kcl/segStart.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										43
									
								
								docs/kcl/segStartX.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										44
									
								
								docs/kcl/segStartY.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						
							
								
								
									
										40856
									
								
								docs/kcl/std.json
									
									
									
									
									
								
							
							
						
						| @ -23,11 +23,11 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `Literal`|  | No | | | `type` |enum: `Literal`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)|  | No | | | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)|  | No | | ||||||
| | `raw` |`string`|  | No | | | `raw` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -43,10 +43,10 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | | | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `name` |`string`|  | No | | | `name` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -62,12 +62,12 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `BinaryExpression`|  | No | | | `type` |enum: `BinaryExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)|  | No | | | `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)|  | No | | ||||||
| | `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | | | `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | | ||||||
| | `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | | | `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -83,12 +83,12 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `CallExpression`|  | No | | | `type` |enum: `CallExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `callee` |[`Identifier`](/docs/kcl/types/Identifier)|  | No | | | `callee` |[`Identifier`](/docs/kcl/types/Identifier)|  | No | | ||||||
| | `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | | | `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | | ||||||
| | `optional` |`boolean`|  | No | | | `optional` |`boolean`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -104,11 +104,11 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `UnaryExpression`|  | No | | | `type` |enum: `UnaryExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)|  | No | | | `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)|  | No | | ||||||
| | `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | | | `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -124,12 +124,12 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `MemberExpression`|  | No | | | `type` |enum: `MemberExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)|  | No | | | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)|  | No | | ||||||
| | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)|  | No | | | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)|  | No | | ||||||
| | `computed` |`boolean`|  | No | | | `computed` |`boolean`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -145,13 +145,13 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `IfExpression`|  | No | | | `type` |enum: `IfExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `cond` |[`Expr`](/docs/kcl/types/Expr)|  | No | | | `cond` |[`Expr`](/docs/kcl/types/Expr)|  | No | | ||||||
| | `then_val` |[`Program`](/docs/kcl/types/Program)|  | No | | | `then_val` |[`Program`](/docs/kcl/types/Program)|  | No | | ||||||
| | `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`|  | No | | | `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`|  | No | | ||||||
| | `final_else` |[`Program`](/docs/kcl/types/Program)|  | No | | | `final_else` |[`Program`](/docs/kcl/types/Program)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
|  | |||||||
| @ -23,12 +23,12 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `ImportStatement`|  | No | | | `type` |enum: `ImportStatement`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`|  | No | | | `items` |`[` [`ImportItem`](/docs/kcl/types/ImportItem) `]`|  | No | | ||||||
| | `path` |`string`|  | No | | | `path` |`string`|  | No | | ||||||
| | `raw_path` |`string`|  | No | | | `raw_path` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -44,10 +44,10 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `ExpressionStatement`|  | No | | | `type` |enum: `ExpressionStatement`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `expression` |[`Expr`](/docs/kcl/types/Expr)|  | No | | | `expression` |[`Expr`](/docs/kcl/types/Expr)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -63,12 +63,12 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `VariableDeclaration`|  | No | | | `type` |enum: `VariableDeclaration`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`|  | No | | | `declarations` |`[` [`VariableDeclarator`](/docs/kcl/types/VariableDeclarator) `]`|  | No | | ||||||
| | `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)|  | No | | | `visibility` |[`ItemVisibility`](/docs/kcl/types/ItemVisibility)|  | No | | ||||||
| | `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)|  | No | | | `kind` |[`VariableKind`](/docs/kcl/types/VariableKind)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -84,10 +84,10 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `ReturnStatement`|  | No | | | `type` |enum: `ReturnStatement`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `argument` |[`Expr`](/docs/kcl/types/Expr)|  | No | | | `argument` |[`Expr`](/docs/kcl/types/Expr)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
|  | |||||||
| @ -15,10 +15,10 @@ layout: manual | |||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `cond` |[`Expr`](/docs/kcl/types/Expr)|  | No | | | `cond` |[`Expr`](/docs/kcl/types/Expr)|  | No | | ||||||
| | `then_val` |[`Program`](/docs/kcl/types/Program)|  | No | | | `then_val` |[`Program`](/docs/kcl/types/Program)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -16,6 +16,6 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `bindings` |`object`|  | No | | | `bindings` |`object`|  | No | | ||||||
| | `parent` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | | | `parent` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -24,11 +24,11 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `Literal`|  | No | | | `type` |enum: `Literal`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No | | | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `raw` |`string`|  | No | | | `raw` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -44,10 +44,10 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | | | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `name` |`string`|  | No | | | `name` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -63,10 +63,10 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)|  | No | | | `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `value` |`string`|  | No | | | `value` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -82,12 +82,12 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `BinaryExpression`|  | No | | | `type` |enum: `BinaryExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No | | | `operator` |[`BinaryOperator`](/docs/kcl/types/BinaryOperator)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | | `left` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | | `right` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -103,11 +103,11 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)|  | No | | | `type` |enum: [`FunctionExpression`](/docs/kcl/types/FunctionExpression)|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`|  | No | | | `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`|  | No | | ||||||
| | `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | | `body` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -123,12 +123,12 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `CallExpression`|  | No | | | `type` |enum: `CallExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No | | | `callee` |[`Identifier`](/docs/kcl/types/Identifier)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | | | `arguments` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | | ||||||
| | `optional` |`boolean`|  | No | | | `optional` |`boolean`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -144,11 +144,11 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `PipeExpression`|  | No | | | `type` |enum: `PipeExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | | | `body` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | | ||||||
| | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -164,9 +164,9 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `PipeSubstitution`|  | No | | | `type` |enum: `PipeSubstitution`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -182,11 +182,11 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `ArrayExpression`|  | No | | | `type` |enum: `ArrayExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | | | `elements` |`[` [`Expr`](/docs/kcl/types/Expr) `]`|  | No | | ||||||
| | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -202,12 +202,12 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `ArrayRangeExpression`|  | No | | | `type` |enum: `ArrayRangeExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | | | `startElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | | | `endElement` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `endInclusive` |`boolean`| Is the `end_element` included in the range? | No | | | `endInclusive` |`boolean`| Is the `end_element` included in the range? | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -223,11 +223,11 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `ObjectExpression`|  | No | | | `type` |enum: `ObjectExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`|  | No | | | `properties` |`[` [`ObjectProperty`](/docs/kcl/types/ObjectProperty) `]`|  | No | | ||||||
| | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -243,12 +243,12 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `MemberExpression`|  | No | | | `type` |enum: `MemberExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No | | | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No | | | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `computed` |`boolean`|  | No | | | `computed` |`boolean`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -264,11 +264,11 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `UnaryExpression`|  | No | | | `type` |enum: `UnaryExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No | | | `operator` |[`UnaryOperator`](/docs/kcl/types/UnaryOperator)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | | `argument` |[`BinaryPart`](/docs/kcl/types/BinaryPart)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -284,13 +284,13 @@ An expression can be evaluated to yield a single KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `IfExpression`|  | No | | | `type` |enum: `IfExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | | | `cond` |[`Expr`](/docs/kcl/types/Expr)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | | `then_val` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`|  | No | | | `else_ifs` |`[` [`ElseIf`](/docs/kcl/types/ElseIf) `]`|  | No | | ||||||
| | `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | | `final_else` |[`Program`](/docs/kcl/types/Program)| An expression can be evaluated to yield a single KCL value. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -307,8 +307,8 @@ KCL value for an optional parameter which was not given an argument. (remember, | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `None`|  | No | | | `type` |enum: `None`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | | | `start` |`integer`|  | No | | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
|  | |||||||
| @ -15,10 +15,10 @@ layout: manual | |||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`|  | No | | | `params` |`[` [`Parameter`](/docs/kcl/types/Parameter) `]`|  | No | | ||||||
| | `body` |[`Program`](/docs/kcl/types/Program)|  | No | | | `body` |[`Program`](/docs/kcl/types/Program)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -15,9 +15,9 @@ layout: manual | |||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `name` |`string`|  | No | | | `name` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -17,8 +17,8 @@ layout: manual | |||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No | | | `name` |[`Identifier`](/docs/kcl/types/Identifier)| Name of the item to import. | No | | ||||||
| | `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No | | | `alias` |[`Identifier`](/docs/kcl/types/Identifier)| Rename the item using an identifier after "as". | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										16
									
								
								docs/kcl/types/KclNone.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,16 @@ | |||||||
|  | --- | ||||||
|  | title: "KclNone" | ||||||
|  | excerpt: "KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application)." | ||||||
|  | layout: manual | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | KCL value for an optional parameter which was not given an argument. (remember, parameters are in the function declaration, arguments are in the function call/application). | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -23,8 +23,110 @@ Any KCL value. | |||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `UserVal`|  | No | | | `type` |enum: `Uuid`|  | No | | ||||||
| | `value` |``|  | No | | | `value` |`string`|  | No | | ||||||
|  | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: `Bool`|  | No | | ||||||
|  | | `value` |`boolean`|  | No | | ||||||
|  | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: `Number`|  | No | | ||||||
|  | | `value` |`number`|  | No | | ||||||
|  | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: `Int`|  | No | | ||||||
|  | | `value` |`integer`|  | No | | ||||||
|  | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: `String`|  | No | | ||||||
|  | | `value` |`string`|  | No | | ||||||
|  | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: `Array`|  | No | | ||||||
|  | | `value` |`[` [`KclValue`](/docs/kcl/types/KclValue) `]`|  | No | | ||||||
|  | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: `Object`|  | No | | ||||||
|  | | `value` |`object`|  | No | | ||||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -59,10 +161,10 @@ Any KCL value. | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)|  | No | | | `type` |enum: [`TagDeclarator`](/docs/kcl/types#tag-declaration)|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `value` |`string`|  | No | | | `value` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -111,6 +213,38 @@ A face. | |||||||
| | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: [`Sketch`](/docs/kcl/types/Sketch)|  | No | | ||||||
|  | | `value` |[`Sketch`](/docs/kcl/types/Sketch)| Any KCL value. | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: `Sketches`|  | No | | ||||||
|  | | `value` |`[` [`Sketch`](/docs/kcl/types/Sketch) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| An solid is a collection of extrude surfaces. | An solid is a collection of extrude surfaces. | ||||||
|  |  | ||||||
| @ -190,6 +324,23 @@ Data for an imported geometry. | |||||||
|  |  | ||||||
| ---- | ---- | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `type` |enum: [`KclNone`](/docs/kcl/types/KclNone)|  | No | | ||||||
|  | | `value` |[`KclNone`](/docs/kcl/types/KclNone)| Any KCL value. | No | | ||||||
|  | | `__meta` |`[` [`Metadata`](/docs/kcl/types/Metadata) `]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ---- | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -23,10 +23,10 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | | | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `name` |`string`|  | No | | | `name` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -42,11 +42,11 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `Literal`|  | No | | | `type` |enum: `Literal`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)|  | No | | | `value` |[`LiteralValue`](/docs/kcl/types/LiteralValue)|  | No | | ||||||
| | `raw` |`string`|  | No | | | `raw` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
|  | |||||||
| @ -23,12 +23,12 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: `MemberExpression`|  | No | | | `type` |enum: `MemberExpression`|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)|  | No | | | `object` |[`MemberObject`](/docs/kcl/types/MemberObject)|  | No | | ||||||
| | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)|  | No | | | `property` |[`LiteralIdentifier`](/docs/kcl/types/LiteralIdentifier)|  | No | | ||||||
| | `computed` |`boolean`|  | No | | | `computed` |`boolean`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
| @ -44,10 +44,10 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | | | `type` |enum: [`Identifier`](/docs/kcl/types/Identifier)|  | No | | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `name` |`string`|  | No | | | `name` |`string`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| ---- | ---- | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `nonCodeNodes` |`object`|  | No | | | `nonCodeNodes` |`object`|  | No | | ||||||
| | `start` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`|  | No | | | `startNodes` |`[` [`NonCodeNode`](/docs/kcl/types/NonCodeNode) `]`|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -15,9 +15,9 @@ layout: manual | |||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)|  | No | | | `value` |[`NonCodeValue`](/docs/kcl/types/NonCodeValue)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -15,10 +15,10 @@ layout: manual | |||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `key` |[`Identifier`](/docs/kcl/types/Identifier)|  | No | | | `key` |[`Identifier`](/docs/kcl/types/Identifier)|  | No | | ||||||
| | `value` |[`Expr`](/docs/kcl/types/Expr)|  | No | | | `value` |[`Expr`](/docs/kcl/types/Expr)|  | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
							
								
								
									
										24
									
								
								docs/kcl/types/PolygonData.md
									
									
									
									
									
										Normal file
									
								
							
							
						
						| @ -0,0 +1,24 @@ | |||||||
|  | --- | ||||||
|  | title: "PolygonData" | ||||||
|  | excerpt: "Data for drawing a polygon" | ||||||
|  | layout: manual | ||||||
|  | --- | ||||||
|  |  | ||||||
|  | Data for drawing a polygon | ||||||
|  |  | ||||||
|  | **Type:** `object` | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  |  | ||||||
|  | ## Properties | ||||||
|  |  | ||||||
|  | | Property | Type | Description | Required | | ||||||
|  | |----------|------|-------------|----------| | ||||||
|  | | `radius` |`number`| The radius of the polygon | No | | ||||||
|  | | `numSides` |`integer`| The number of sides in the polygon | No | | ||||||
|  | | `center` |`[number, number]`| The center point of the polygon | No | | ||||||
|  | | `inscribed` |`boolean`| Whether the polygon is inscribed (true) or circumscribed (false) about a circle with the specified radius | No | | ||||||
|  |  | ||||||
|  |  | ||||||
| @ -16,10 +16,10 @@ A KCL program top level, or function body. | |||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`|  | No | | | `body` |`[` [`BodyItem`](/docs/kcl/types/BodyItem) `]`|  | No | | ||||||
| | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No | | | `nonCodeMeta` |[`NonCodeMeta`](/docs/kcl/types/NonCodeMeta)| A KCL program top level, or function body. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -16,7 +16,7 @@ layout: manual | |||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`|  | No | | | `environments` |`[` [`Environment`](/docs/kcl/types/Environment) `]`|  | No | | ||||||
| | `currentEnv` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | | | `currentEnv` |`integer`|  | No | | ||||||
| | `return` |[`KclValue`](/docs/kcl/types/KclValue)|  | No | | | `return` |[`KclValue`](/docs/kcl/types/KclValue)|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -15,10 +15,10 @@ layout: manual | |||||||
|  |  | ||||||
| | Property | Type | Description | Required | | | Property | Type | Description | Required | | ||||||
| |----------|------|-------------|----------| | |----------|------|-------------|----------| | ||||||
| | `start` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `end` |[`EnvironmentRef`](/docs/kcl/types/EnvironmentRef)|  | No | |  | ||||||
| | `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No | | | `id` |[`Identifier`](/docs/kcl/types/Identifier)| The identifier of the variable. | No | | ||||||
| | `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No | | | `init` |[`Expr`](/docs/kcl/types/Expr)| The value of the variable. | No | | ||||||
| | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | | `digest` |`[, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`, `integer`]`|  | No | | ||||||
|  | | `start` |`integer`|  | No | | ||||||
|  | | `end` |`integer`|  | No | | ||||||
|  |  | ||||||
|  |  | ||||||
|  | |||||||
| @ -67,15 +67,15 @@ async function doBasicSketch(page: Page, openPanes: string[]) { | |||||||
|   if (openPanes.includes('code')) { |   if (openPanes.includes('code')) { | ||||||
|     await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') |     await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt(${commonPoints.startAt}, %) |   |> startProfileAt(${commonPoints.startAt}, %) | ||||||
|   |> line([${commonPoints.num1}, 0], %)`) |   |> xLine(${commonPoints.num1}, %)`) | ||||||
|   } |   } | ||||||
|   await page.waitForTimeout(500) |   await page.waitForTimeout(500) | ||||||
|   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) |   await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) | ||||||
|   if (openPanes.includes('code')) { |   if (openPanes.includes('code')) { | ||||||
|     await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') |     await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt(${commonPoints.startAt}, %) |   |> startProfileAt(${commonPoints.startAt}, %) | ||||||
|   |> line([${commonPoints.num1}, 0], %) |   |> xLine(${commonPoints.num1}, %) | ||||||
|   |> line([0, ${commonPoints.num1 + 0.01}], %)`) |   |> yLine(${commonPoints.num1 + 0.01}, %)`) | ||||||
|   } else { |   } else { | ||||||
|     await page.waitForTimeout(500) |     await page.waitForTimeout(500) | ||||||
|   } |   } | ||||||
| @ -84,9 +84,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) { | |||||||
|   if (openPanes.includes('code')) { |   if (openPanes.includes('code')) { | ||||||
|     await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') |     await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt(${commonPoints.startAt}, %) |   |> startProfileAt(${commonPoints.startAt}, %) | ||||||
|   |> line([${commonPoints.num1}, 0], %) |   |> xLine(${commonPoints.num1}, %) | ||||||
|   |> line([0, ${commonPoints.num1 + 0.01}], %) |   |> yLine(${commonPoints.num1 + 0.01}, %) | ||||||
|   |> line([-${commonPoints.num2}, 0], %)`) |   |> xLine(${commonPoints.num2 * -1}, %)`) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   // deselect line tool |   // deselect line tool | ||||||
| @ -142,9 +142,9 @@ async function doBasicSketch(page: Page, openPanes: string[]) { | |||||||
|   await u.openKclCodePanel() |   await u.openKclCodePanel() | ||||||
|   await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') |   await expect(u.codeLocator).toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt(${commonPoints.startAt}, %) |   |> startProfileAt(${commonPoints.startAt}, %) | ||||||
|   |> line([${commonPoints.num1}, 0], %, $seg01) |   |> xLine(${commonPoints.num1}, %, $seg01) | ||||||
|   |> line([0, ${commonPoints.num1 + 0.01}], %) |   |> yLine(${commonPoints.num1 + 0.01}, %) | ||||||
|   |> angledLine([180, segLen(seg01)], %)`) |   |> xLine(-segLen(seg01), %)`) | ||||||
| } | } | ||||||
|  |  | ||||||
| test.describe('Basic sketch', () => { | test.describe('Basic sketch', () => { | ||||||
|  | |||||||
| @ -632,16 +632,18 @@ test.describe('Editor tests', () => { | |||||||
|  |  | ||||||
|       await u.waitForAuthSkipAppStart() |       await u.waitForAuthSkipAppStart() | ||||||
|  |  | ||||||
|       // this test might be brittle as we add and remove functions |  | ||||||
|       // but should also be easy to update. |  | ||||||
|       // tests clicking on an option, selection the first option |       // tests clicking on an option, selection the first option | ||||||
|       // and arrowing down to an option |       // and arrowing down to an option | ||||||
|  |  | ||||||
|       await u.codeLocator.click() |       await u.codeLocator.click() | ||||||
|       await page.keyboard.type('sketch001 = start') |       await page.keyboard.type('sketch001 = start') | ||||||
|  |  | ||||||
|       // expect there to be six auto complete options |       // expect there to be some auto complete options | ||||||
|       await expect(page.locator('.cm-completionLabel')).toHaveCount(8) |       // exact number depends on the KCL stdlib, so let's just check it's > 0 for now. | ||||||
|  |       await expect(async () => { | ||||||
|  |         const children = await page.locator('.cm-completionLabel').count() | ||||||
|  |         expect(children).toBeGreaterThan(0) | ||||||
|  |       }).toPass() | ||||||
|       // this makes sure we can accept a completion with click |       // this makes sure we can accept a completion with click | ||||||
|       await page.getByText('startSketchOn').click() |       await page.getByText('startSketchOn').click() | ||||||
|       await page.keyboard.type("'XZ'") |       await page.keyboard.type("'XZ'") | ||||||
| @ -692,6 +694,9 @@ test.describe('Editor tests', () => { | |||||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') |         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> startProfileAt([3.14, 12], %) |     |> startProfileAt([3.14, 12], %) | ||||||
|     |> xLine(5, %) // lin`) |     |> xLine(5, %) // lin`) | ||||||
|  |  | ||||||
|  |       // expect there to be no KCL errors | ||||||
|  |       await expect(page.locator('.cm-lint-marker-error')).toHaveCount(0) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     test('with tab to accept the completion', async ({ page }) => { |     test('with tab to accept the completion', async ({ page }) => { | ||||||
| @ -985,7 +990,7 @@ test.describe('Editor tests', () => { | |||||||
|     |> extrude(5, %)`) |     |> extrude(5, %)`) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test( |   test.fixme( | ||||||
|     `Can use the import stdlib function on a local OBJ file`, |     `Can use the import stdlib function on a local OBJ file`, | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ browserName }, testInfo) => { |     async ({ browserName }, testInfo) => { | ||||||
|  | |||||||
| @ -3,6 +3,7 @@ import { test, expect } from './fixtures/fixtureSetup' | |||||||
| import * as fsp from 'fs/promises' | import * as fsp from 'fs/promises' | ||||||
| import * as fs from 'fs' | import * as fs from 'fs' | ||||||
| import { | import { | ||||||
|  |   createProject, | ||||||
|   executorInputPath, |   executorInputPath, | ||||||
|   getUtils, |   getUtils, | ||||||
|   setup, |   setup, | ||||||
| @ -25,10 +26,6 @@ test.describe('integrations tests', () => { | |||||||
|     'Creating a new file or switching file while in sketchMode should exit sketchMode', |     'Creating a new file or switching file while in sketchMode should exit sketchMode', | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ tronApp, homePage, scene, editor, toolbar }) => { |     async ({ tronApp, homePage, scene, editor, toolbar }) => { | ||||||
|       test.skip( |  | ||||||
|         process.platform === 'win32', |  | ||||||
|         'windows times out will waiting for the execution indicator?' |  | ||||||
|       ) |  | ||||||
|       await tronApp.initialise({ |       await tronApp.initialise({ | ||||||
|         fixtures: { homePage, scene, editor, toolbar }, |         fixtures: { homePage, scene, editor, toolbar }, | ||||||
|         folderSetupFn: async (dir) => { |         folderSetupFn: async (dir) => { | ||||||
| @ -54,7 +51,6 @@ test.describe('integrations tests', () => { | |||||||
|           sortBy: 'last-modified-desc', |           sortBy: 'last-modified-desc', | ||||||
|         }) |         }) | ||||||
|         await homePage.openProject('test-sample') |         await homePage.openProject('test-sample') | ||||||
|         // windows times out here, hence the skip above |  | ||||||
|         await scene.waitForExecutionDone() |         await scene.waitForExecutionDone() | ||||||
|       }) |       }) | ||||||
|       await test.step('enter sketch mode', async () => { |       await test.step('enter sketch mode', async () => { | ||||||
| @ -70,10 +66,13 @@ test.describe('integrations tests', () => { | |||||||
|         await toolbar.editSketch() |         await toolbar.editSketch() | ||||||
|         await expect(toolbar.exitSketchBtn).toBeVisible() |         await expect(toolbar.exitSketchBtn).toBeVisible() | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|  |       const fileName = 'Untitled.kcl' | ||||||
|       await test.step('check sketch mode is exited when creating new file', async () => { |       await test.step('check sketch mode is exited when creating new file', async () => { | ||||||
|         await toolbar.fileTreeBtn.click() |         await toolbar.fileTreeBtn.click() | ||||||
|         await toolbar.expectFileTreeState(['main.kcl']) |         await toolbar.expectFileTreeState(['main.kcl']) | ||||||
|         await toolbar.createFile({ wait: true }) |  | ||||||
|  |         await toolbar.createFile({ fileName, waitForToastToDisappear: true }) | ||||||
|  |  | ||||||
|         // check we're out of sketch mode |         // check we're out of sketch mode | ||||||
|         await expect(toolbar.exitSketchBtn).not.toBeVisible() |         await expect(toolbar.exitSketchBtn).not.toBeVisible() | ||||||
| @ -92,10 +91,10 @@ test.describe('integrations tests', () => { | |||||||
|         }) |         }) | ||||||
|         await toolbar.editSketch() |         await toolbar.editSketch() | ||||||
|         await expect(toolbar.exitSketchBtn).toBeVisible() |         await expect(toolbar.exitSketchBtn).toBeVisible() | ||||||
|         await toolbar.expectFileTreeState(['main.kcl', 'Untitled.kcl']) |         await toolbar.expectFileTreeState(['main.kcl', fileName]) | ||||||
|       }) |       }) | ||||||
|       await test.step('check sketch mode is exited when opening a different file', async () => { |       await test.step('check sketch mode is exited when opening a different file', async () => { | ||||||
|         await toolbar.openFile('untitled.kcl', { wait: false }) |         await toolbar.openFile(fileName, { wait: false }) | ||||||
|  |  | ||||||
|         // check we're out of sketch mode |         // check we're out of sketch mode | ||||||
|         await expect(toolbar.exitSketchBtn).not.toBeVisible() |         await expect(toolbar.exitSketchBtn).not.toBeVisible() | ||||||
| @ -108,26 +107,21 @@ test.describe('when using the file tree to', () => { | |||||||
|   const fromFile = 'main.kcl' |   const fromFile = 'main.kcl' | ||||||
|   const toFile = 'hello.kcl' |   const toFile = 'hello.kcl' | ||||||
|  |  | ||||||
|   test( |   test.fixme( | ||||||
|     `rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`, |     `rename ${fromFile} to ${toFile}, and doesn't crash on reload and settings load`, | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ browser: _, tronApp }, testInfo) => { |     async ({ browser: _, tronApp }, testInfo) => { | ||||||
|       await tronApp.initialise() |       await tronApp.initialise() | ||||||
|  |  | ||||||
|       const { |       const { panesOpen, pasteCodeInEditor, renameFile, editorTextMatches } = | ||||||
|         panesOpen, |         await getUtils(tronApp.page, test) | ||||||
|         createAndSelectProject, |  | ||||||
|         pasteCodeInEditor, |  | ||||||
|         renameFile, |  | ||||||
|         editorTextMatches, |  | ||||||
|       } = await getUtils(tronApp.page, test) |  | ||||||
|  |  | ||||||
|       await tronApp.page.setViewportSize({ width: 1200, height: 500 }) |       await tronApp.page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|       tronApp.page.on('console', console.log) |       tronApp.page.on('console', console.log) | ||||||
|  |  | ||||||
|       await panesOpen(['files', 'code']) |       await panesOpen(['files', 'code']) | ||||||
|  |  | ||||||
|       await createAndSelectProject('project-000') |       await createProject({ name: 'project-000', page: tronApp.page }) | ||||||
|  |  | ||||||
|       // File the main.kcl with contents |       // File the main.kcl with contents | ||||||
|       const kclCube = await fsp.readFile( |       const kclCube = await fsp.readFile( | ||||||
| @ -161,21 +155,20 @@ test.describe('when using the file tree to', () => { | |||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   test( |   test.fixme( | ||||||
|     `create many new untitled files they increment their names`, |     `create many new untitled files they increment their names`, | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ browser: _, tronApp }, testInfo) => { |     async ({ browser: _, tronApp }, testInfo) => { | ||||||
|       await tronApp.initialise() |       await tronApp.initialise() | ||||||
|  |  | ||||||
|       const { panesOpen, createAndSelectProject, createNewFile } = |       const { panesOpen, createNewFile } = await getUtils(tronApp.page, test) | ||||||
|         await getUtils(tronApp.page, test) |  | ||||||
|  |  | ||||||
|       await tronApp.page.setViewportSize({ width: 1200, height: 500 }) |       await tronApp.page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|       tronApp.page.on('console', console.log) |       tronApp.page.on('console', console.log) | ||||||
|  |  | ||||||
|       await panesOpen(['files']) |       await panesOpen(['files']) | ||||||
|  |  | ||||||
|       await createAndSelectProject('project-000') |       await createProject({ name: 'project-000', page: tronApp.page }) | ||||||
|  |  | ||||||
|       await createNewFile('') |       await createNewFile('') | ||||||
|       await createNewFile('') |       await createNewFile('') | ||||||
| @ -198,62 +191,74 @@ test.describe('when using the file tree to', () => { | |||||||
|   test( |   test( | ||||||
|     'create a new file with the same name as an existing file cancels the operation', |     'create a new file with the same name as an existing file cancels the operation', | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ browser: _, tronApp }, testInfo) => { |     async ( | ||||||
|       await tronApp.initialise() |       { browser: _, tronApp, homePage, scene, editor, toolbar }, | ||||||
|  |       testInfo | ||||||
|  |     ) => { | ||||||
|  |       const projectName = 'cube' | ||||||
|  |       const mainFile = 'main.kcl' | ||||||
|  |       const secondFile = 'cylinder.kcl' | ||||||
|  |       const kclCube = await fsp.readFile(executorInputPath('cube.kcl'), 'utf-8') | ||||||
|  |       const kclCylinder = await fsp.readFile( | ||||||
|  |         executorInputPath('cylinder.kcl'), | ||||||
|  |         'utf-8' | ||||||
|  |       ) | ||||||
|  |       await tronApp.initialise({ | ||||||
|  |         fixtures: { homePage, scene, editor, toolbar }, | ||||||
|  |         folderSetupFn: async (dir) => { | ||||||
|  |           const cubeDir = join(dir, projectName) | ||||||
|  |           await fsp.mkdir(cubeDir, { recursive: true }) | ||||||
|  |           await fsp.copyFile( | ||||||
|  |             executorInputPath('cube.kcl'), | ||||||
|  |             join(cubeDir, mainFile) | ||||||
|  |           ) | ||||||
|  |           await fsp.copyFile( | ||||||
|  |             executorInputPath('cylinder.kcl'), | ||||||
|  |             join(cubeDir, secondFile) | ||||||
|  |           ) | ||||||
|  |         }, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|       const { |       const { | ||||||
|         openKclCodePanel, |  | ||||||
|         openFilePanel, |         openFilePanel, | ||||||
|         createAndSelectProject, |  | ||||||
|         pasteCodeInEditor, |  | ||||||
|         createNewFileAndSelect, |  | ||||||
|         renameFile, |         renameFile, | ||||||
|         selectFile, |         selectFile, | ||||||
|         editorTextMatches, |         editorTextMatches, | ||||||
|  |         waitForPageLoad, | ||||||
|       } = await getUtils(tronApp.page, _test) |       } = await getUtils(tronApp.page, _test) | ||||||
|  |  | ||||||
|       await tronApp.page.setViewportSize({ width: 1200, height: 500 }) |       await test.step(`Setup: Open project and navigate to ${secondFile}`, async () => { | ||||||
|       tronApp.page.on('console', console.log) |         await homePage.expectState({ | ||||||
|  |           projectCards: [ | ||||||
|  |             { | ||||||
|  |               title: projectName, | ||||||
|  |               fileCount: 2, | ||||||
|  |               folderCount: 2, // TODO: This is a pre-existing bug, there are no folders within the project | ||||||
|  |             }, | ||||||
|  |           ], | ||||||
|  |           sortBy: 'last-modified-desc', | ||||||
|  |         }) | ||||||
|  |         await homePage.openProject(projectName) | ||||||
|  |         await waitForPageLoad() | ||||||
|  |         await openFilePanel() | ||||||
|  |         await selectFile(secondFile) | ||||||
|  |       }) | ||||||
|  |  | ||||||
|       await createAndSelectProject('project-000') |       await test.step(`Attempt to rename ${secondFile} to ${mainFile}`, async () => { | ||||||
|       await openKclCodePanel() |         await renameFile(secondFile, mainFile) | ||||||
|       await openFilePanel() |       }) | ||||||
|       // File the main.kcl with contents |  | ||||||
|       const kclCube = await fsp.readFile( |  | ||||||
|         'src/wasm-lib/tests/executor/inputs/cube.kcl', |  | ||||||
|         'utf-8' |  | ||||||
|       ) |  | ||||||
|       await pasteCodeInEditor(kclCube) |  | ||||||
|  |  | ||||||
|       // TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk. |       await test.step(`Postcondition: ${mainFile} still has the original content`, async () => { | ||||||
|       await tronApp.page.waitForTimeout(2000) |         await selectFile(mainFile) | ||||||
|  |  | ||||||
|       const kcl1 = 'main.kcl' |  | ||||||
|       const kcl2 = '2.kcl' |  | ||||||
|       await createNewFileAndSelect(kcl2) |  | ||||||
|       const kclCylinder = await fsp.readFile( |  | ||||||
|         'src/wasm-lib/tests/executor/inputs/cylinder.kcl', |  | ||||||
|         'utf-8' |  | ||||||
|       ) |  | ||||||
|       await pasteCodeInEditor(kclCylinder) |  | ||||||
|  |  | ||||||
|       // TODO: We have a timeout of 1s between edits to write to disk. If you reload the page too quickly it won't write to disk. |  | ||||||
|       await tronApp.page.waitForTimeout(2000) |  | ||||||
|  |  | ||||||
|       await renameFile(kcl2, kcl1) |  | ||||||
|  |  | ||||||
|       await test.step(`Postcondition: ${kcl1} still has the original content`, async () => { |  | ||||||
|         await selectFile(kcl1) |  | ||||||
|         await editorTextMatches(kclCube) |         await editorTextMatches(kclCube) | ||||||
|       }) |       }) | ||||||
|       await tronApp.page.waitForTimeout(500) |  | ||||||
|  |  | ||||||
|       await test.step(`Postcondition: ${kcl2} still exists with the original content`, async () => { |       await test.step(`Postcondition: ${secondFile} still exists with the original content`, async () => { | ||||||
|         await selectFile(kcl2) |         await selectFile(secondFile) | ||||||
|         await editorTextMatches(kclCylinder) |         await editorTextMatches(kclCylinder) | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await tronApp?.close?.() |       await tronApp.close() | ||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
| @ -263,20 +268,15 @@ test.describe('when using the file tree to', () => { | |||||||
|     async ({ browser: _, tronApp }, testInfo) => { |     async ({ browser: _, tronApp }, testInfo) => { | ||||||
|       await tronApp.initialise() |       await tronApp.initialise() | ||||||
|  |  | ||||||
|       const { |       const { panesOpen, pasteCodeInEditor, deleteFile, editorTextMatches } = | ||||||
|         panesOpen, |         await getUtils(tronApp.page, _test) | ||||||
|         createAndSelectProject, |  | ||||||
|         pasteCodeInEditor, |  | ||||||
|         deleteFile, |  | ||||||
|         editorTextMatches, |  | ||||||
|       } = await getUtils(tronApp.page, _test) |  | ||||||
|  |  | ||||||
|       await tronApp.page.setViewportSize({ width: 1200, height: 500 }) |       await tronApp.page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|       tronApp.page.on('console', console.log) |       tronApp.page.on('console', console.log) | ||||||
|  |  | ||||||
|       await panesOpen(['files', 'code']) |       await panesOpen(['files', 'code']) | ||||||
|  |  | ||||||
|       await createAndSelectProject('project-000') |       await createProject({ name: 'project-000', page: tronApp.page }) | ||||||
|       // File the main.kcl with contents |       // File the main.kcl with contents | ||||||
|       const kclCube = await fsp.readFile( |       const kclCube = await fsp.readFile( | ||||||
|         'src/wasm-lib/tests/executor/inputs/cube.kcl', |         'src/wasm-lib/tests/executor/inputs/cube.kcl', | ||||||
| @ -284,11 +284,11 @@ test.describe('when using the file tree to', () => { | |||||||
|       ) |       ) | ||||||
|       await pasteCodeInEditor(kclCube) |       await pasteCodeInEditor(kclCube) | ||||||
|  |  | ||||||
|       const kcl1 = 'main.kcl' |       const mainFile = 'main.kcl' | ||||||
|  |  | ||||||
|       await deleteFile(kcl1) |       await deleteFile(mainFile) | ||||||
|  |  | ||||||
|       await test.step(`Postcondition: ${kcl1} is recreated but has no content`, async () => { |       await test.step(`Postcondition: ${mainFile} is recreated but has no content`, async () => { | ||||||
|         await editorTextMatches('') |         await editorTextMatches('') | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
| @ -296,7 +296,7 @@ test.describe('when using the file tree to', () => { | |||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   test( |   test.fixme( | ||||||
|     'loading small file, then large, then back to small', |     'loading small file, then large, then back to small', | ||||||
|     { |     { | ||||||
|       tag: '@electron', |       tag: '@electron', | ||||||
| @ -306,7 +306,6 @@ test.describe('when using the file tree to', () => { | |||||||
|  |  | ||||||
|       const { |       const { | ||||||
|         panesOpen, |         panesOpen, | ||||||
|         createAndSelectProject, |  | ||||||
|         pasteCodeInEditor, |         pasteCodeInEditor, | ||||||
|         createNewFile, |         createNewFile, | ||||||
|         openDebugPanel, |         openDebugPanel, | ||||||
| @ -318,7 +317,7 @@ test.describe('when using the file tree to', () => { | |||||||
|       tronApp.page.on('console', console.log) |       tronApp.page.on('console', console.log) | ||||||
|  |  | ||||||
|       await panesOpen(['files', 'code']) |       await panesOpen(['files', 'code']) | ||||||
|       await createAndSelectProject('project-000') |       await createProject({ name: 'project-000', page: tronApp.page }) | ||||||
|  |  | ||||||
|       // Create a small file |       // Create a small file | ||||||
|       const kclCube = await fsp.readFile( |       const kclCube = await fsp.readFile( | ||||||
| @ -722,7 +721,7 @@ _test.describe('Renaming in the file tree', () => { | |||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await _test.step('Rename the folder', async () => { |       await _test.step('Rename the folder', async () => { | ||||||
|         await page.waitForTimeout(60000) |         await page.waitForTimeout(1000) | ||||||
|         await folderToRename.click({ button: 'right' }) |         await folderToRename.click({ button: 'right' }) | ||||||
|         await _expect(renameMenuItem).toBeVisible() |         await _expect(renameMenuItem).toBeVisible() | ||||||
|         await renameMenuItem.click() |         await renameMenuItem.click() | ||||||
|  | |||||||
| @ -1,6 +1,11 @@ | |||||||
| import type { Page, Locator } from '@playwright/test' | import type { Page, Locator } from '@playwright/test' | ||||||
| import { expect } from '@playwright/test' | import { expect } from '@playwright/test' | ||||||
| import { sansWhitespace } from '../test-utils' | import { | ||||||
|  |   closePane, | ||||||
|  |   checkIfPaneIsOpen, | ||||||
|  |   openPane, | ||||||
|  |   sansWhitespace, | ||||||
|  | } from '../test-utils' | ||||||
|  |  | ||||||
| interface EditorState { | interface EditorState { | ||||||
|   activeLines: Array<string> |   activeLines: Array<string> | ||||||
| @ -11,6 +16,7 @@ interface EditorState { | |||||||
| export class EditorFixture { | export class EditorFixture { | ||||||
|   public page: Page |   public page: Page | ||||||
|  |  | ||||||
|  |   private paneButtonTestId = 'code-pane-button' | ||||||
|   private diagnosticsTooltip!: Locator |   private diagnosticsTooltip!: Locator | ||||||
|   private diagnosticsGutterIcon!: Locator |   private diagnosticsGutterIcon!: Locator | ||||||
|   private codeContent!: Locator |   private codeContent!: Locator | ||||||
| @ -31,19 +37,32 @@ export class EditorFixture { | |||||||
|  |  | ||||||
|   private _expectEditorToContain = |   private _expectEditorToContain = | ||||||
|     (not = false) => |     (not = false) => | ||||||
|     ( |     async ( | ||||||
|       code: string, |       code: string, | ||||||
|       { |       { | ||||||
|         shouldNormalise = false, |         shouldNormalise = false, | ||||||
|         timeout = 5_000, |         timeout = 5_000, | ||||||
|       }: { shouldNormalise?: boolean; timeout?: number } = {} |       }: { shouldNormalise?: boolean; timeout?: number } = {} | ||||||
|     ) => { |     ) => { | ||||||
|  |       const wasPaneOpen = await this.checkIfPaneIsOpen() | ||||||
|  |       if (!wasPaneOpen) { | ||||||
|  |         await this.openPane() | ||||||
|  |       } | ||||||
|  |       const resetPane = async () => { | ||||||
|  |         if (!wasPaneOpen) { | ||||||
|  |           await this.closePane() | ||||||
|  |         } | ||||||
|  |       } | ||||||
|       if (!shouldNormalise) { |       if (!shouldNormalise) { | ||||||
|         const expectStart = expect(this.codeContent) |         const expectStart = expect(this.codeContent) | ||||||
|         if (not) { |         if (not) { | ||||||
|           return expectStart.not.toContainText(code, { timeout }) |           const result = await expectStart.not.toContainText(code, { timeout }) | ||||||
|  |           await resetPane() | ||||||
|  |           return result | ||||||
|         } |         } | ||||||
|         return expectStart.toContainText(code, { timeout }) |         const result = await expectStart.toContainText(code, { timeout }) | ||||||
|  |         await resetPane() | ||||||
|  |         return result | ||||||
|       } |       } | ||||||
|       const normalisedCode = code.replaceAll(/\s+/g, '').trim() |       const normalisedCode = code.replaceAll(/\s+/g, '').trim() | ||||||
|       const expectStart = expect.poll( |       const expectStart = expect.poll( | ||||||
| @ -56,9 +75,13 @@ export class EditorFixture { | |||||||
|         } |         } | ||||||
|       ) |       ) | ||||||
|       if (not) { |       if (not) { | ||||||
|         return expectStart.not.toContain(normalisedCode) |         const result = await expectStart.not.toContain(normalisedCode) | ||||||
|  |         await resetPane() | ||||||
|  |         return result | ||||||
|       } |       } | ||||||
|       return expectStart.toContain(normalisedCode) |       const result = await expectStart.toContain(normalisedCode) | ||||||
|  |       await resetPane() | ||||||
|  |       return result | ||||||
|     } |     } | ||||||
|   expectEditor = { |   expectEditor = { | ||||||
|     toContain: this._expectEditorToContain(), |     toContain: this._expectEditorToContain(), | ||||||
| @ -115,4 +138,13 @@ export class EditorFixture { | |||||||
|     code = code.replace(findCode, replaceCode) |     code = code.replace(findCode, replaceCode) | ||||||
|     await this.codeContent.fill(code) |     await this.codeContent.fill(code) | ||||||
|   } |   } | ||||||
|  |   checkIfPaneIsOpen() { | ||||||
|  |     return checkIfPaneIsOpen(this.page, this.paneButtonTestId) | ||||||
|  |   } | ||||||
|  |   closePane() { | ||||||
|  |     return closePane(this.page, this.paneButtonTestId) | ||||||
|  |   } | ||||||
|  |   openPane() { | ||||||
|  |     return openPane(this.page, this.paneButtonTestId) | ||||||
|  |   } | ||||||
| } | } | ||||||
|  | |||||||
| @ -20,6 +20,7 @@ export class AuthenticatedApp { | |||||||
|   public readonly page: Page |   public readonly page: Page | ||||||
|   public readonly context: BrowserContext |   public readonly context: BrowserContext | ||||||
|   public readonly testInfo: TestInfo |   public readonly testInfo: TestInfo | ||||||
|  |   public readonly viewPortSize = { width: 1000, height: 500 } | ||||||
|  |  | ||||||
|   constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { |   constructor(context: BrowserContext, page: Page, testInfo: TestInfo) { | ||||||
|     this.page = page |     this.page = page | ||||||
| @ -36,7 +37,7 @@ export class AuthenticatedApp { | |||||||
|       ;(window as any).playwrightSkipFilePicker = true |       ;(window as any).playwrightSkipFilePicker = true | ||||||
|     }, code) |     }, code) | ||||||
|  |  | ||||||
|     await this.page.setViewportSize({ width: 1000, height: 500 }) |     await this.page.setViewportSize(this.viewPortSize) | ||||||
|  |  | ||||||
|     await u.waitForAuthSkipAppStart() |     await u.waitForAuthSkipAppStart() | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -10,7 +10,13 @@ import { | |||||||
| } from '../test-utils' | } from '../test-utils' | ||||||
|  |  | ||||||
| type mouseParams = { | type mouseParams = { | ||||||
|   pixelDiff: number |   pixelDiff?: number | ||||||
|  | } | ||||||
|  | type mouseDragToParams = mouseParams & { | ||||||
|  |   fromPoint: { x: number; y: number } | ||||||
|  | } | ||||||
|  | type mouseDragFromParams = mouseParams & { | ||||||
|  |   toPoint: { x: number; y: number } | ||||||
| } | } | ||||||
|  |  | ||||||
| type SceneSerialised = { | type SceneSerialised = { | ||||||
| @ -20,6 +26,13 @@ type SceneSerialised = { | |||||||
|   } |   } | ||||||
| } | } | ||||||
|  |  | ||||||
|  | type ClickHandler = (clickParams?: mouseParams) => Promise<void | boolean> | ||||||
|  | type MoveHandler = (moveParams?: mouseParams) => Promise<void | boolean> | ||||||
|  | type DragToHandler = (dragParams: mouseDragToParams) => Promise<void | boolean> | ||||||
|  | type DragFromHandler = ( | ||||||
|  |   dragParams: mouseDragFromParams | ||||||
|  | ) => Promise<void | boolean> | ||||||
|  |  | ||||||
| export class SceneFixture { | export class SceneFixture { | ||||||
|   public page: Page |   public page: Page | ||||||
|  |  | ||||||
| @ -55,7 +68,7 @@ export class SceneFixture { | |||||||
|     x: number, |     x: number, | ||||||
|     y: number, |     y: number, | ||||||
|     { steps }: { steps: number } = { steps: 20 } |     { steps }: { steps: number } = { steps: 20 } | ||||||
|   ) => |   ): [ClickHandler, MoveHandler] => | ||||||
|     [ |     [ | ||||||
|       (clickParams?: mouseParams) => { |       (clickParams?: mouseParams) => { | ||||||
|         if (clickParams?.pixelDiff) { |         if (clickParams?.pixelDiff) { | ||||||
| @ -78,6 +91,47 @@ export class SceneFixture { | |||||||
|         return this.page.mouse.move(x, y, { steps }) |         return this.page.mouse.move(x, y, { steps }) | ||||||
|       }, |       }, | ||||||
|     ] as const |     ] as const | ||||||
|  |   makeDragHelpers = ( | ||||||
|  |     x: number, | ||||||
|  |     y: number, | ||||||
|  |     { steps }: { steps: number } = { steps: 20 } | ||||||
|  |   ): [DragToHandler, DragFromHandler] => | ||||||
|  |     [ | ||||||
|  |       (dragToParams: mouseDragToParams) => { | ||||||
|  |         if (dragToParams?.pixelDiff) { | ||||||
|  |           return doAndWaitForImageDiff( | ||||||
|  |             this.page, | ||||||
|  |             () => | ||||||
|  |               this.page.dragAndDrop('#stream', '#stream', { | ||||||
|  |                 sourcePosition: dragToParams.fromPoint, | ||||||
|  |                 targetPosition: { x, y }, | ||||||
|  |               }), | ||||||
|  |             dragToParams.pixelDiff | ||||||
|  |           ) | ||||||
|  |         } | ||||||
|  |         return this.page.dragAndDrop('#stream', '#stream', { | ||||||
|  |           sourcePosition: dragToParams.fromPoint, | ||||||
|  |           targetPosition: { x, y }, | ||||||
|  |         }) | ||||||
|  |       }, | ||||||
|  |       (dragFromParams: mouseDragFromParams) => { | ||||||
|  |         if (dragFromParams?.pixelDiff) { | ||||||
|  |           return doAndWaitForImageDiff( | ||||||
|  |             this.page, | ||||||
|  |             () => | ||||||
|  |               this.page.dragAndDrop('#stream', '#stream', { | ||||||
|  |                 sourcePosition: { x, y }, | ||||||
|  |                 targetPosition: dragFromParams.toPoint, | ||||||
|  |               }), | ||||||
|  |             dragFromParams.pixelDiff | ||||||
|  |           ) | ||||||
|  |         } | ||||||
|  |         return this.page.dragAndDrop('#stream', '#stream', { | ||||||
|  |           sourcePosition: { x, y }, | ||||||
|  |           targetPosition: dragFromParams.toPoint, | ||||||
|  |         }) | ||||||
|  |       }, | ||||||
|  |     ] as const | ||||||
|  |  | ||||||
|   /** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene. |   /** Likely no where, there's a chance it will click something in the scene, depending what you have in the scene. | ||||||
|    * |    * | ||||||
| @ -141,7 +195,7 @@ export class SceneFixture { | |||||||
|   } |   } | ||||||
|  |  | ||||||
|   waitForExecutionDone = async () => { |   waitForExecutionDone = async () => { | ||||||
|     await expect(this.exeIndicator).toBeVisible() |     await expect(this.exeIndicator).toBeVisible({ timeout: 30000 }) | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   expectPixelColor = async ( |   expectPixelColor = async ( | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ export class ToolbarFixture { | |||||||
|  |  | ||||||
|   extrudeButton!: Locator |   extrudeButton!: Locator | ||||||
|   startSketchBtn!: Locator |   startSketchBtn!: Locator | ||||||
|  |   lineBtn!: Locator | ||||||
|   rectangleBtn!: Locator |   rectangleBtn!: Locator | ||||||
|   exitSketchBtn!: Locator |   exitSketchBtn!: Locator | ||||||
|   editSketchBtn!: Locator |   editSketchBtn!: Locator | ||||||
| @ -15,6 +16,7 @@ export class ToolbarFixture { | |||||||
|   fileCreateToast!: Locator |   fileCreateToast!: Locator | ||||||
|   filePane!: Locator |   filePane!: Locator | ||||||
|   exeIndicator!: Locator |   exeIndicator!: Locator | ||||||
|  |   treeInputField!: Locator | ||||||
|  |  | ||||||
|   constructor(page: Page) { |   constructor(page: Page) { | ||||||
|     this.page = page |     this.page = page | ||||||
| @ -24,11 +26,13 @@ export class ToolbarFixture { | |||||||
|     this.page = page |     this.page = page | ||||||
|     this.extrudeButton = page.getByTestId('extrude') |     this.extrudeButton = page.getByTestId('extrude') | ||||||
|     this.startSketchBtn = page.getByTestId('sketch') |     this.startSketchBtn = page.getByTestId('sketch') | ||||||
|  |     this.lineBtn = page.getByTestId('line') | ||||||
|     this.rectangleBtn = page.getByTestId('corner-rectangle') |     this.rectangleBtn = page.getByTestId('corner-rectangle') | ||||||
|     this.exitSketchBtn = page.getByTestId('sketch-exit') |     this.exitSketchBtn = page.getByTestId('sketch-exit') | ||||||
|     this.editSketchBtn = page.getByText('Edit Sketch') |     this.editSketchBtn = page.getByText('Edit Sketch') | ||||||
|     this.fileTreeBtn = page.locator('[id="files-button-holder"]') |     this.fileTreeBtn = page.locator('[id="files-button-holder"]') | ||||||
|     this.createFileBtn = page.getByTestId('create-file-button') |     this.createFileBtn = page.getByTestId('create-file-button') | ||||||
|  |     this.treeInputField = page.getByTestId('tree-input-field') | ||||||
|  |  | ||||||
|     this.filePane = page.locator('#files-pane') |     this.filePane = page.locator('#files-pane') | ||||||
|     this.fileCreateToast = page.getByText('Successfully created') |     this.fileCreateToast = page.getByText('Successfully created') | ||||||
| @ -57,10 +61,15 @@ export class ToolbarFixture { | |||||||
|   expectFileTreeState = async (expected: string[]) => { |   expectFileTreeState = async (expected: string[]) => { | ||||||
|     await expect.poll(this._serialiseFileTree).toEqual(expected) |     await expect.poll(this._serialiseFileTree).toEqual(expected) | ||||||
|   } |   } | ||||||
|   createFile = async ({ wait }: { wait: boolean } = { wait: false }) => { |   createFile = async (args: { | ||||||
|  |     fileName: string | ||||||
|  |     waitForToastToDisappear: boolean | ||||||
|  |   }) => { | ||||||
|     await this.createFileBtn.click() |     await this.createFileBtn.click() | ||||||
|  |     await this.treeInputField.fill(args.fileName) | ||||||
|  |     await this.treeInputField.press('Enter') | ||||||
|     await expect(this.fileCreateToast).toBeVisible() |     await expect(this.fileCreateToast).toBeVisible() | ||||||
|     if (wait) { |     if (args.waitForToastToDisappear) { | ||||||
|       await this.fileCreateToast.waitFor({ state: 'detached' }) |       await this.fileCreateToast.waitFor({ state: 'detached' }) | ||||||
|     } |     } | ||||||
|   } |   } | ||||||
|  | |||||||
| @ -18,7 +18,7 @@ export const isErrorWhitelisted = (exception: Error) => { | |||||||
|     { |     { | ||||||
|       name: '"{"kind"', |       name: '"{"kind"', | ||||||
|       message: |       message: | ||||||
|         '"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"', |         '"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}"', | ||||||
|       stack: '', |       stack: '', | ||||||
|       foundInSpec: 'e2e/playwright/testing-settings.spec.ts', |       foundInSpec: 'e2e/playwright/testing-settings.spec.ts', | ||||||
|       project: 'Google Chrome', |       project: 'Google Chrome', | ||||||
| @ -156,8 +156,8 @@ export const isErrorWhitelisted = (exception: Error) => { | |||||||
|     { |     { | ||||||
|       name: 'Unhandled Promise Rejection', |       name: 'Unhandled Promise Rejection', | ||||||
|       message: |       message: | ||||||
|         '{"kind":"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}', |         '{"kind":"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: `JsValue(undefined)`"}', | ||||||
|       stack: `Unhandled Promise Rejection: {"kind":"engine","sourceRanges":[[0,0]],"msg":"Failed to get string from response from engine: \`JsValue(undefined)\`"} |       stack: `Unhandled Promise Rejection: {"kind":"engine","sourceRanges":[[0,0,0]],"msg":"Failed to get string from response from engine: \`JsValue(undefined)\`"} | ||||||
|     at unknown (http://localhost:3000/src/lang/std/engineConnection.ts:1245:26)`, |     at unknown (http://localhost:3000/src/lang/std/engineConnection.ts:1245:26)`, | ||||||
|       foundInSpec: |       foundInSpec: | ||||||
|         'e2e/playwright/onboarding-tests.spec.ts Click through each onboarding step', |         'e2e/playwright/onboarding-tests.spec.ts Click through each onboarding step', | ||||||
| @ -253,7 +253,7 @@ export const isErrorWhitelisted = (exception: Error) => { | |||||||
|     { |     { | ||||||
|       name: '{"kind"', |       name: '{"kind"', | ||||||
|       stack: ``, |       stack: ``, | ||||||
|       message: `engine","sourceRanges":[[0,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`, |       message: `engine","sourceRanges":[[0,0,0]],"msg":"Failed to wait for promise from engine: JsValue(\\"Force interrupt, executionIsStale, new AST requested\\")"}`, | ||||||
|       project: 'Google Chrome', |       project: 'Google Chrome', | ||||||
|       foundInSpec: 'e2e/playwright/testing-settings.spec.ts', |       foundInSpec: 'e2e/playwright/testing-settings.spec.ts', | ||||||
|     }, |     }, | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ import { | |||||||
|   setupElectron, |   setupElectron, | ||||||
|   tearDown, |   tearDown, | ||||||
|   executorInputPath, |   executorInputPath, | ||||||
|  |   createProject, | ||||||
| } from './test-utils' | } from './test-utils' | ||||||
| import { bracket } from 'lib/exampleKcl' | import { bracket } from 'lib/exampleKcl' | ||||||
| import { onboardingPaths } from 'routes/Onboarding/paths' | import { onboardingPaths } from 'routes/Onboarding/paths' | ||||||
| @ -74,13 +75,8 @@ test.describe('Onboarding tests', () => { | |||||||
|       const viewportSize = { width: 1200, height: 500 } |       const viewportSize = { width: 1200, height: 500 } | ||||||
|       await page.setViewportSize(viewportSize) |       await page.setViewportSize(viewportSize) | ||||||
|  |  | ||||||
|       // Locators and constants |  | ||||||
|       const newProjectButton = page.getByRole('button', { name: 'New project' }) |  | ||||||
|       const projectLink = page.getByTestId('project-link') |  | ||||||
|  |  | ||||||
|       await test.step(`Create a project and open to the onboarding`, async () => { |       await test.step(`Create a project and open to the onboarding`, async () => { | ||||||
|         await newProjectButton.click() |         await createProject({ name: 'project-link', page }) | ||||||
|         await projectLink.click() |  | ||||||
|         await test.step(`Ensure the engine connection works by testing the sketch button`, async () => { |         await test.step(`Ensure the engine connection works by testing the sketch button`, async () => { | ||||||
|           await u.waitForPageLoad() |           await u.waitForPageLoad() | ||||||
|         }) |         }) | ||||||
| @ -425,7 +421,9 @@ test( | |||||||
|     const restartConfirmationButton = page.getByRole('button', { |     const restartConfirmationButton = page.getByRole('button', { | ||||||
|       name: 'Make a new project', |       name: 'Make a new project', | ||||||
|     }) |     }) | ||||||
|     const tutorialProjectIndicator = page.getByText('Tutorial Project 00') |     const tutorialProjectIndicator = page | ||||||
|  |       .getByTestId('project-sidebar-toggle') | ||||||
|  |       .filter({ hasText: 'Tutorial Project 00' }) | ||||||
|     const tutorialModalText = page.getByText('Welcome to Modeling App!') |     const tutorialModalText = page.getByText('Welcome to Modeling App!') | ||||||
|     const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' }) |     const tutorialDismissButton = page.getByRole('button', { name: 'Dismiss' }) | ||||||
|     const userMenuButton = page.getByTestId('user-sidebar-toggle') |     const userMenuButton = page.getByTestId('user-sidebar-toggle') | ||||||
|  | |||||||
| @ -451,3 +451,103 @@ sketch002 = startSketchOn(extrude001, seg03) | |||||||
|     } |     } | ||||||
|   ) |   ) | ||||||
| }) | }) | ||||||
|  |  | ||||||
|  | test(`Verify axis, origin, and horizontal snapping`, async ({ | ||||||
|  |   app, | ||||||
|  |   editor, | ||||||
|  |   toolbar, | ||||||
|  |   scene, | ||||||
|  | }) => { | ||||||
|  |   // Constants and locators | ||||||
|  |   // These are mappings from screenspace to KCL coordinates, | ||||||
|  |   // until we merge in our coordinate system helpers | ||||||
|  |   const xzPlane = [ | ||||||
|  |     app.viewPortSize.width * 0.65, | ||||||
|  |     app.viewPortSize.height * 0.3, | ||||||
|  |   ] as const | ||||||
|  |   const originSloppy = { | ||||||
|  |     screen: [ | ||||||
|  |       app.viewPortSize.width / 2 + 3, // 3px off the center of the screen | ||||||
|  |       app.viewPortSize.height / 2, | ||||||
|  |     ], | ||||||
|  |     kcl: [0, 0], | ||||||
|  |   } as const | ||||||
|  |   const xAxisSloppy = { | ||||||
|  |     screen: [ | ||||||
|  |       app.viewPortSize.width * 0.75, | ||||||
|  |       app.viewPortSize.height / 2 - 3, // 3px off the X-axis | ||||||
|  |     ], | ||||||
|  |     kcl: [16.95, 0], | ||||||
|  |   } as const | ||||||
|  |   const offYAxis = { | ||||||
|  |     screen: [ | ||||||
|  |       app.viewPortSize.width * 0.6, // Well off the Y-axis, out of snapping range | ||||||
|  |       app.viewPortSize.height * 0.3, | ||||||
|  |     ], | ||||||
|  |     kcl: [6.78, 6.78], | ||||||
|  |   } as const | ||||||
|  |   const yAxisSloppy = { | ||||||
|  |     screen: [ | ||||||
|  |       app.viewPortSize.width / 2 + 5, // 5px off the Y-axis | ||||||
|  |       app.viewPortSize.height * 0.3, | ||||||
|  |     ], | ||||||
|  |     kcl: [0, 6.78], | ||||||
|  |   } as const | ||||||
|  |   const [clickOnXzPlane, moveToXzPlane] = scene.makeMouseHelpers(...xzPlane) | ||||||
|  |   const [clickOriginSloppy] = scene.makeMouseHelpers(...originSloppy.screen) | ||||||
|  |   const [clickXAxisSloppy, moveXAxisSloppy] = scene.makeMouseHelpers( | ||||||
|  |     ...xAxisSloppy.screen | ||||||
|  |   ) | ||||||
|  |   const [dragToOffYAxis, dragFromOffAxis] = scene.makeDragHelpers( | ||||||
|  |     ...offYAxis.screen | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   const expectedCodeSnippets = { | ||||||
|  |     sketchOnXzPlane: `sketch001 = startSketchOn('XZ')`, | ||||||
|  |     pointAtOrigin: `startProfileAt([${originSloppy.kcl[0]}, ${originSloppy.kcl[1]}], %)`, | ||||||
|  |     segmentOnXAxis: `xLine(${xAxisSloppy.kcl[0]}, %)`, | ||||||
|  |     afterSegmentDraggedOffYAxis: `startProfileAt([${offYAxis.kcl[0]}, ${offYAxis.kcl[1]}], %)`, | ||||||
|  |     afterSegmentDraggedOnYAxis: `startProfileAt([${yAxisSloppy.kcl[0]}, ${yAxisSloppy.kcl[1]}], %)`, | ||||||
|  |   } | ||||||
|  |  | ||||||
|  |   await app.initialise() | ||||||
|  |  | ||||||
|  |   await test.step(`Start a sketch on the XZ plane`, async () => { | ||||||
|  |     await editor.closePane() | ||||||
|  |     await toolbar.startSketchPlaneSelection() | ||||||
|  |     await moveToXzPlane() | ||||||
|  |     await clickOnXzPlane() | ||||||
|  |     // timeout wait for engine animation is unavoidable | ||||||
|  |     await app.page.waitForTimeout(600) | ||||||
|  |     await editor.expectEditor.toContain(expectedCodeSnippets.sketchOnXzPlane) | ||||||
|  |   }) | ||||||
|  |   await test.step(`Place a point a few pixels off the middle, verify it still snaps to 0,0`, async () => { | ||||||
|  |     await clickOriginSloppy() | ||||||
|  |     await editor.expectEditor.toContain(expectedCodeSnippets.pointAtOrigin) | ||||||
|  |   }) | ||||||
|  |   await test.step(`Add a segment on x-axis after moving the mouse a bit, verify it snaps`, async () => { | ||||||
|  |     await moveXAxisSloppy() | ||||||
|  |     await clickXAxisSloppy() | ||||||
|  |     await editor.expectEditor.toContain(expectedCodeSnippets.segmentOnXAxis) | ||||||
|  |   }) | ||||||
|  |   await test.step(`Unequip line tool`, async () => { | ||||||
|  |     await toolbar.lineBtn.click() | ||||||
|  |     await expect(toolbar.lineBtn).not.toHaveAttribute('aria-pressed', 'true') | ||||||
|  |   }) | ||||||
|  |   await test.step(`Drag the origin point up and to the right, verify it's past snapping`, async () => { | ||||||
|  |     await dragToOffYAxis({ | ||||||
|  |       fromPoint: { x: originSloppy.screen[0], y: originSloppy.screen[1] }, | ||||||
|  |     }) | ||||||
|  |     await editor.expectEditor.toContain( | ||||||
|  |       expectedCodeSnippets.afterSegmentDraggedOffYAxis | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
|  |   await test.step(`Drag the origin point left to the y-axis, verify it snaps back`, async () => { | ||||||
|  |     await dragFromOffAxis({ | ||||||
|  |       toPoint: { x: yAxisSloppy.screen[0], y: yAxisSloppy.screen[1] }, | ||||||
|  |     }) | ||||||
|  |     await editor.expectEditor.toContain( | ||||||
|  |       expectedCodeSnippets.afterSegmentDraggedOnYAxis | ||||||
|  |     ) | ||||||
|  |   }) | ||||||
|  | }) | ||||||
|  | |||||||
| @ -7,7 +7,7 @@ import { | |||||||
|   Paths, |   Paths, | ||||||
|   setupElectron, |   setupElectron, | ||||||
|   tearDown, |   tearDown, | ||||||
|   createProjectAndRenameIt, |   createProject, | ||||||
| } from './test-utils' | } from './test-utils' | ||||||
| import fsp from 'fs/promises' | import fsp from 'fs/promises' | ||||||
| import fs from 'fs' | import fs from 'fs' | ||||||
| @ -503,21 +503,261 @@ test( | |||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  |  | ||||||
|  | test.describe(`Project management commands`, () => { | ||||||
|  |   test( | ||||||
|  |     `Rename from project page`, | ||||||
|  |     { tag: '@electron' }, | ||||||
|  |     async ({ browserName }, testInfo) => { | ||||||
|  |       const projectName = `my_project_to_rename` | ||||||
|  |       const { electronApp, page } = await setupElectron({ | ||||||
|  |         testInfo, | ||||||
|  |         folderSetupFn: async (dir) => { | ||||||
|  |           await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) | ||||||
|  |           await fsp.copyFile( | ||||||
|  |             'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl', | ||||||
|  |             `${dir}/${projectName}/main.kcl` | ||||||
|  |           ) | ||||||
|  |         }, | ||||||
|  |       }) | ||||||
|  |       const u = await getUtils(page) | ||||||
|  |  | ||||||
|  |       // Constants and locators | ||||||
|  |       const projectHomeLink = page.getByTestId('project-link') | ||||||
|  |       const commandButton = page.getByRole('button', { name: 'Commands' }) | ||||||
|  |       const commandOption = page.getByRole('option', { name: 'rename project' }) | ||||||
|  |       const projectNameOption = page.getByRole('option', { name: projectName }) | ||||||
|  |       const projectRenamedName = `project-000` | ||||||
|  |       // const projectMenuButton = page.getByTestId('project-sidebar-toggle') | ||||||
|  |       const commandContinueButton = page.getByRole('button', { | ||||||
|  |         name: 'Continue', | ||||||
|  |       }) | ||||||
|  |       const commandSubmitButton = page.getByRole('button', { | ||||||
|  |         name: 'Submit command', | ||||||
|  |       }) | ||||||
|  |       const toastMessage = page.getByText(`Successfully renamed`) | ||||||
|  |  | ||||||
|  |       await test.step(`Setup`, async () => { | ||||||
|  |         await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |         page.on('console', console.log) | ||||||
|  |  | ||||||
|  |         await projectHomeLink.click() | ||||||
|  |         await u.waitForPageLoad() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Run rename command via command palette`, async () => { | ||||||
|  |         await commandButton.click() | ||||||
|  |         await commandOption.click() | ||||||
|  |         await projectNameOption.click() | ||||||
|  |  | ||||||
|  |         await expect(commandContinueButton).toBeVisible() | ||||||
|  |         await commandContinueButton.click() | ||||||
|  |  | ||||||
|  |         await expect(commandSubmitButton).toBeVisible() | ||||||
|  |         await commandSubmitButton.click() | ||||||
|  |  | ||||||
|  |         await expect(toastMessage).toBeVisible() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       // TODO: in future I'd like the behavior to be to | ||||||
|  |       // navigate to the new project's page directly, | ||||||
|  |       // see ProjectContextProvider.tsx:158 | ||||||
|  |       await test.step(`Check the project was renamed and we navigated home`, async () => { | ||||||
|  |         await expect(projectHomeLink.first()).toBeVisible() | ||||||
|  |         await expect(projectHomeLink.first()).toContainText(projectRenamedName) | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  |  | ||||||
|  |   test( | ||||||
|  |     `Delete from project page`, | ||||||
|  |     { tag: '@electron' }, | ||||||
|  |     async ({ browserName: _ }, testInfo) => { | ||||||
|  |       const projectName = `my_project_to_delete` | ||||||
|  |       const { electronApp, page } = await setupElectron({ | ||||||
|  |         testInfo, | ||||||
|  |         folderSetupFn: async (dir) => { | ||||||
|  |           await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) | ||||||
|  |           await fsp.copyFile( | ||||||
|  |             'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl', | ||||||
|  |             `${dir}/${projectName}/main.kcl` | ||||||
|  |           ) | ||||||
|  |         }, | ||||||
|  |       }) | ||||||
|  |       const u = await getUtils(page) | ||||||
|  |  | ||||||
|  |       // Constants and locators | ||||||
|  |       const projectHomeLink = page.getByTestId('project-link') | ||||||
|  |       const commandButton = page.getByRole('button', { name: 'Commands' }) | ||||||
|  |       const commandOption = page.getByRole('option', { name: 'delete project' }) | ||||||
|  |       const projectNameOption = page.getByRole('option', { name: projectName }) | ||||||
|  |       const commandWarning = page.getByText('Are you sure you want to delete?') | ||||||
|  |       const commandSubmitButton = page.getByRole('button', { | ||||||
|  |         name: 'Submit command', | ||||||
|  |       }) | ||||||
|  |       const toastMessage = page.getByText(`Successfully deleted`) | ||||||
|  |       const noProjectsMessage = page.getByText('No Projects found') | ||||||
|  |  | ||||||
|  |       await test.step(`Setup`, async () => { | ||||||
|  |         await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |         page.on('console', console.log) | ||||||
|  |  | ||||||
|  |         await projectHomeLink.click() | ||||||
|  |         await u.waitForPageLoad() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Run delete command via command palette`, async () => { | ||||||
|  |         await commandButton.click() | ||||||
|  |         await commandOption.click() | ||||||
|  |         await projectNameOption.click() | ||||||
|  |  | ||||||
|  |         await expect(commandWarning).toBeVisible() | ||||||
|  |         await expect(commandSubmitButton).toBeVisible() | ||||||
|  |         await commandSubmitButton.click() | ||||||
|  |  | ||||||
|  |         await expect(toastMessage).toBeVisible() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Check the project was deleted and we navigated home`, async () => { | ||||||
|  |         await expect(noProjectsMessage).toBeVisible() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  |   test( | ||||||
|  |     `Rename from home page`, | ||||||
|  |     { tag: '@electron' }, | ||||||
|  |     async ({ browserName: _ }, testInfo) => { | ||||||
|  |       const projectName = `my_project_to_rename` | ||||||
|  |       const { electronApp, page } = await setupElectron({ | ||||||
|  |         testInfo, | ||||||
|  |         folderSetupFn: async (dir) => { | ||||||
|  |           await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) | ||||||
|  |           await fsp.copyFile( | ||||||
|  |             'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl', | ||||||
|  |             `${dir}/${projectName}/main.kcl` | ||||||
|  |           ) | ||||||
|  |         }, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       // Constants and locators | ||||||
|  |       const projectHomeLink = page.getByTestId('project-link') | ||||||
|  |       const commandButton = page.getByRole('button', { name: 'Commands' }) | ||||||
|  |       const commandOption = page.getByRole('option', { name: 'rename project' }) | ||||||
|  |       const projectNameOption = page.getByRole('option', { name: projectName }) | ||||||
|  |       const projectRenamedName = `project-000` | ||||||
|  |       const commandContinueButton = page.getByRole('button', { | ||||||
|  |         name: 'Continue', | ||||||
|  |       }) | ||||||
|  |       const commandSubmitButton = page.getByRole('button', { | ||||||
|  |         name: 'Submit command', | ||||||
|  |       }) | ||||||
|  |       const toastMessage = page.getByText(`Successfully renamed`) | ||||||
|  |  | ||||||
|  |       await test.step(`Setup`, async () => { | ||||||
|  |         await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |         page.on('console', console.log) | ||||||
|  |         await expect(projectHomeLink).toBeVisible() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Run rename command via command palette`, async () => { | ||||||
|  |         await commandButton.click() | ||||||
|  |         await commandOption.click() | ||||||
|  |         await projectNameOption.click() | ||||||
|  |  | ||||||
|  |         await expect(commandContinueButton).toBeVisible() | ||||||
|  |         await commandContinueButton.click() | ||||||
|  |  | ||||||
|  |         await expect(commandSubmitButton).toBeVisible() | ||||||
|  |         await commandSubmitButton.click() | ||||||
|  |  | ||||||
|  |         await expect(toastMessage).toBeVisible() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Check the project was renamed`, async () => { | ||||||
|  |         await expect( | ||||||
|  |           page.getByRole('link', { name: projectRenamedName }) | ||||||
|  |         ).toBeVisible() | ||||||
|  |         await expect(projectHomeLink).not.toHaveText(projectName) | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  |   test( | ||||||
|  |     `Delete from home page`, | ||||||
|  |     { tag: '@electron' }, | ||||||
|  |     async ({ browserName: _ }, testInfo) => { | ||||||
|  |       const projectName = `my_project_to_delete` | ||||||
|  |       const { electronApp, page } = await setupElectron({ | ||||||
|  |         testInfo, | ||||||
|  |         folderSetupFn: async (dir) => { | ||||||
|  |           await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) | ||||||
|  |           await fsp.copyFile( | ||||||
|  |             'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl', | ||||||
|  |             `${dir}/${projectName}/main.kcl` | ||||||
|  |           ) | ||||||
|  |         }, | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       // Constants and locators | ||||||
|  |       const projectHomeLink = page.getByTestId('project-link') | ||||||
|  |       const commandButton = page.getByRole('button', { name: 'Commands' }) | ||||||
|  |       const commandOption = page.getByRole('option', { name: 'delete project' }) | ||||||
|  |       const projectNameOption = page.getByRole('option', { name: projectName }) | ||||||
|  |       const commandWarning = page.getByText('Are you sure you want to delete?') | ||||||
|  |       const commandSubmitButton = page.getByRole('button', { | ||||||
|  |         name: 'Submit command', | ||||||
|  |       }) | ||||||
|  |       const toastMessage = page.getByText(`Successfully deleted`) | ||||||
|  |       const noProjectsMessage = page.getByText('No Projects found') | ||||||
|  |  | ||||||
|  |       await test.step(`Setup`, async () => { | ||||||
|  |         await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |         page.on('console', console.log) | ||||||
|  |         await expect(projectHomeLink).toBeVisible() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Run delete command via command palette`, async () => { | ||||||
|  |         await commandButton.click() | ||||||
|  |         await commandOption.click() | ||||||
|  |         await projectNameOption.click() | ||||||
|  |  | ||||||
|  |         await expect(commandWarning).toBeVisible() | ||||||
|  |         await expect(commandSubmitButton).toBeVisible() | ||||||
|  |         await commandSubmitButton.click() | ||||||
|  |  | ||||||
|  |         await expect(toastMessage).toBeVisible() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Check the project was deleted`, async () => { | ||||||
|  |         await expect(projectHomeLink).not.toBeVisible() | ||||||
|  |         await expect(noProjectsMessage).toBeVisible() | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await electronApp.close() | ||||||
|  |     } | ||||||
|  |   ) | ||||||
|  | }) | ||||||
|  |  | ||||||
| test( | test( | ||||||
|   'File in the file pane should open with a single click', |   'File in the file pane should open with a single click', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|   async ({ browserName }, testInfo) => { |   async ({ browserName }, testInfo) => { | ||||||
|  |     const projectName = 'router-template-slate' | ||||||
|     const { electronApp, page } = await setupElectron({ |     const { electronApp, page } = await setupElectron({ | ||||||
|       testInfo, |       testInfo, | ||||||
|       folderSetupFn: async (dir) => { |       folderSetupFn: async (dir) => { | ||||||
|         await fsp.mkdir(`${dir}/router-template-slate`, { recursive: true }) |         await fsp.mkdir(`${dir}/${projectName}`, { recursive: true }) | ||||||
|         await fsp.copyFile( |         await fsp.copyFile( | ||||||
|           'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl', |           'src/wasm-lib/tests/executor/inputs/router-template-slate.kcl', | ||||||
|           `${dir}/router-template-slate/main.kcl` |           `${dir}/${projectName}/main.kcl` | ||||||
|         ) |         ) | ||||||
|         await fsp.copyFile( |         await fsp.copyFile( | ||||||
|           'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl', |           'src/wasm-lib/tests/executor/inputs/focusrite_scarlett_mounting_braket.kcl', | ||||||
|           `${dir}/router-template-slate/otherThingToClickOn.kcl` |           `${dir}/${projectName}/otherThingToClickOn.kcl` | ||||||
|         ) |         ) | ||||||
|       }, |       }, | ||||||
|     }) |     }) | ||||||
| @ -526,7 +766,7 @@ test( | |||||||
|  |  | ||||||
|     page.on('console', console.log) |     page.on('console', console.log) | ||||||
|  |  | ||||||
|     await page.getByText('router-template-slate').click() |     await page.getByText(projectName).click() | ||||||
|     await expect(page.getByTestId('loading')).toBeAttached() |     await expect(page.getByTestId('loading')).toBeAttached() | ||||||
|     await expect(page.getByTestId('loading')).not.toBeAttached({ |     await expect(page.getByTestId('loading')).not.toBeAttached({ | ||||||
|       timeout: 20_000, |       timeout: 20_000, | ||||||
| @ -614,7 +854,7 @@ test( | |||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| test( | test.fixme( | ||||||
|   'Deleting projects, can delete individual project, can still create projects after deleting all', |   'Deleting projects, can delete individual project, can still create projects after deleting all', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|   async ({ browserName }, testInfo) => { |   async ({ browserName }, testInfo) => { | ||||||
| @ -643,7 +883,7 @@ test( | |||||||
|     page.on('console', console.log) |     page.on('console', console.log) | ||||||
|  |  | ||||||
|     await test.step('delete the middle project, i.e. the bracket project', async () => { |     await test.step('delete the middle project, i.e. the bracket project', async () => { | ||||||
|       const project = page.getByText('bracket') |       const project = page.getByTestId('project-link').getByText('bracket') | ||||||
|  |  | ||||||
|       await project.hover() |       await project.hover() | ||||||
|       await project.focus() |       await project.focus() | ||||||
| @ -687,10 +927,10 @@ test( | |||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('Check we can still create a project', async () => { |     await test.step('Check we can still create a project', async () => { | ||||||
|       await page.getByRole('button', { name: 'New project' }).click() |       await createProject({ name: 'project-000', page, returnHome: true }) | ||||||
|       await expect(page.getByText('Successfully created')).toBeVisible() |       await expect( | ||||||
|       await expect(page.getByText('Successfully created')).not.toBeVisible() |         page.getByTestId('project-link').filter({ hasText: 'project-000' }) | ||||||
|       await expect(page.getByText('project-000')).toBeVisible() |       ).toBeVisible() | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await electronApp.close() |     await electronApp.close() | ||||||
| @ -867,17 +1107,16 @@ test.fixme( | |||||||
|     const pointOnModel = { x: 660, y: 250 } |     const pointOnModel = { x: 660, y: 250 } | ||||||
|     const expectedStartCamZPosition = 15633.47 |     const expectedStartCamZPosition = 15633.47 | ||||||
|  |  | ||||||
|  |     // Constants and locators | ||||||
|  |     const projectLinks = page.getByTestId('project-link') | ||||||
|  |  | ||||||
|     // expect to see text "No Projects found" |     // expect to see text "No Projects found" | ||||||
|     await expect(page.getByText('No Projects found')).toBeVisible() |     await expect(page.getByText('No Projects found')).toBeVisible() | ||||||
|  |  | ||||||
|     await page.getByRole('button', { name: 'New project' }).click() |     await createProject({ name: 'project-000', page, returnHome: true }) | ||||||
|  |     await expect(projectLinks.getByText('project-000')).toBeVisible() | ||||||
|  |  | ||||||
|     await expect(page.getByText('Successfully created')).toBeVisible() |     await projectLinks.getByText('project-000').click() | ||||||
|     await expect(page.getByText('Successfully created')).not.toBeVisible() |  | ||||||
|  |  | ||||||
|     await expect(page.getByText('project-000')).toBeVisible() |  | ||||||
|  |  | ||||||
|     await page.getByText('project-000').click() |  | ||||||
|  |  | ||||||
|     await u.waitForPageLoad() |     await u.waitForPageLoad() | ||||||
|  |  | ||||||
| @ -936,16 +1175,10 @@ extrude001 = extrude(200, sketch001)`) | |||||||
|       page.getByRole('button', { name: 'New project' }) |       page.getByRole('button', { name: 'New project' }) | ||||||
|     ).toBeVisible() |     ).toBeVisible() | ||||||
|  |  | ||||||
|     const createProject = async (projectNum: number) => { |  | ||||||
|       await page.getByRole('button', { name: 'New project' }).click() |  | ||||||
|       await expect(page.getByText('Successfully created')).toBeVisible() |  | ||||||
|       await expect(page.getByText('Successfully created')).not.toBeVisible() |  | ||||||
|  |  | ||||||
|       const projectNumStr = projectNum.toString().padStart(3, '0') |  | ||||||
|       await expect(page.getByText(`project-${projectNumStr}`)).toBeVisible() |  | ||||||
|     } |  | ||||||
|     for (let i = 1; i <= 10; i++) { |     for (let i = 1; i <= 10; i++) { | ||||||
|       await createProject(i) |       const name = `project-${i.toString().padStart(3, '0')}` | ||||||
|  |       await createProject({ name, page, returnHome: true }) | ||||||
|  |       await expect(projectLinks.getByText(name)).toBeVisible() | ||||||
|     } |     } | ||||||
|     await electronApp.close() |     await electronApp.close() | ||||||
|   } |   } | ||||||
| @ -1120,11 +1353,10 @@ test( | |||||||
|       await page.getByTestId('settings-close-button').click() |       await page.getByTestId('settings-close-button').click() | ||||||
|  |  | ||||||
|       await expect(page.getByText('No Projects found')).toBeVisible() |       await expect(page.getByText('No Projects found')).toBeVisible() | ||||||
|       await page.getByRole('button', { name: 'New project' }).click() |       await createProject({ name: 'project-000', page, returnHome: true }) | ||||||
|       await expect(page.getByText('Successfully created')).toBeVisible() |       await expect( | ||||||
|       await expect(page.getByText('Successfully created')).not.toBeVisible() |         page.getByTestId('project-link').filter({ hasText: 'project-000' }) | ||||||
|  |       ).toBeVisible() | ||||||
|       await expect(page.getByText(`project-000`)).toBeVisible() |  | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('We can change back to the original root project directory', async () => { |     await test.step('We can change back to the original root project directory', async () => { | ||||||
| @ -1258,13 +1490,13 @@ test( | |||||||
|           'function_sketch.kcl', |           'function_sketch.kcl', | ||||||
|           'function_sketch_with_position.kcl', |           'function_sketch_with_position.kcl', | ||||||
|           'global-tags.kcl', |           'global-tags.kcl', | ||||||
|           'helix_ccw.kcl', |  | ||||||
|           'helix_defaults.kcl', |           'helix_defaults.kcl', | ||||||
|           'helix_defaults_negative_extrude.kcl', |           'helix_defaults_negative_extrude.kcl', | ||||||
|           'helix_with_length.kcl', |           'helix_with_length.kcl', | ||||||
|           'i_shape.kcl', |           'i_shape.kcl', | ||||||
|           'kittycad_svg.kcl', |           'kittycad_svg.kcl', | ||||||
|           'lego.kcl', |           'lego.kcl', | ||||||
|  |           'lsystem.kcl', | ||||||
|           'math.kcl', |           'math.kcl', | ||||||
|           'member_expression_sketch.kcl', |           'member_expression_sketch.kcl', | ||||||
|           'mike_stress_test.kcl', |           'mike_stress_test.kcl', | ||||||
| @ -1437,7 +1669,8 @@ test( | |||||||
|   } |   } | ||||||
| ) | ) | ||||||
|  |  | ||||||
| test( | // Flaky | ||||||
|  | test.fixme( | ||||||
|   'Original project name persist after onboarding', |   'Original project name persist after onboarding', | ||||||
|   { tag: '@electron' }, |   { tag: '@electron' }, | ||||||
|   async ({ browserName }, testInfo) => { |   async ({ browserName }, testInfo) => { | ||||||
| @ -1450,7 +1683,7 @@ test( | |||||||
|     page.on('console', console.log) |     page.on('console', console.log) | ||||||
|  |  | ||||||
|     await test.step('Should create and name a project called wrist brace', async () => { |     await test.step('Should create and name a project called wrist brace', async () => { | ||||||
|       await createProjectAndRenameIt({ name: 'wrist brace', page }) |       await createProject({ name: 'wrist brace', page, returnHome: true }) | ||||||
|     }) |     }) | ||||||
|  |  | ||||||
|     await test.step('Should go through onboarding', async () => { |     await test.step('Should go through onboarding', async () => { | ||||||
|  | |||||||
| @ -115,7 +115,7 @@ test.describe('Sketch tests', () => { | |||||||
|         'persistCode', |         'persistCode', | ||||||
|         `sketch001 = startSketchOn('XZ') |         `sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt([4.61, -14.01], %) |   |> startProfileAt([4.61, -14.01], %) | ||||||
|   |> line([12.73, -0.09], %) |   |> xLine(12.73, %) | ||||||
|   |> tangentialArcTo([24.95, -5.38], %)` |   |> tangentialArcTo([24.95, -5.38], %)` | ||||||
|       ) |       ) | ||||||
|     }) |     }) | ||||||
| @ -156,7 +156,7 @@ test.describe('Sketch tests', () => { | |||||||
|       await expect.poll(u.normalisedEditorCode, { timeout: 1000 }) |       await expect.poll(u.normalisedEditorCode, { timeout: 1000 }) | ||||||
|         .toBe(`sketch001 = startSketchOn('XZ') |         .toBe(`sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt([12.34, -12.34], %) |   |> startProfileAt([12.34, -12.34], %) | ||||||
|   |> line([-12.34, 12.34], %) |   |> yLine(12.34, %) | ||||||
|  |  | ||||||
| `) | `) | ||||||
|     }).toPass({ timeout: 40_000, intervals: [1_000] }) |     }).toPass({ timeout: 40_000, intervals: [1_000] }) | ||||||
| @ -637,7 +637,6 @@ test.describe('Sketch tests', () => { | |||||||
|     |> revolve({ axis: "X" }, %)`) |     |> revolve({ axis: "X" }, %)`) | ||||||
|   }) |   }) | ||||||
|   test('Can add multiple sketches', async ({ page }) => { |   test('Can add multiple sketches', async ({ page }) => { | ||||||
|     test.skip(process.platform === 'darwin', 'Can add multiple sketches') |  | ||||||
|     const u = await getUtils(page) |     const u = await getUtils(page) | ||||||
|     const viewportSize = { width: 1200, height: 500 } |     const viewportSize = { width: 1200, height: 500 } | ||||||
|     await page.setViewportSize(viewportSize) |     await page.setViewportSize(viewportSize) | ||||||
| @ -646,7 +645,7 @@ test.describe('Sketch tests', () => { | |||||||
|     await u.openDebugPanel() |     await u.openDebugPanel() | ||||||
|  |  | ||||||
|     const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 } |     const center = { x: viewportSize.width / 2, y: viewportSize.height / 2 } | ||||||
|     const { toSU, click00r } = getMovementUtils({ center, page }) |     const { toSU, toU, click00r } = getMovementUtils({ center, page }) | ||||||
|  |  | ||||||
|     await expect( |     await expect( | ||||||
|       page.getByRole('button', { name: 'Start Sketch' }) |       page.getByRole('button', { name: 'Start Sketch' }) | ||||||
| @ -675,15 +674,15 @@ test.describe('Sketch tests', () => { | |||||||
|  |  | ||||||
|     await click00r(50, 0) |     await click00r(50, 0) | ||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
|     codeStr += `  |> line(${toSU([50, 0])}, %)` |     codeStr += `  |> xLine(${toU(50, 0)[0]}, %)` | ||||||
|     await expect(u.codeLocator).toHaveText(codeStr) |     await expect(u.codeLocator).toHaveText(codeStr) | ||||||
|  |  | ||||||
|     await click00r(0, 50) |     await click00r(0, 50) | ||||||
|     codeStr += `  |> line(${toSU([0, 50])}, %)` |     codeStr += `  |> yLine(${toU(0, 50)[1]}, %)` | ||||||
|     await expect(u.codeLocator).toHaveText(codeStr) |     await expect(u.codeLocator).toHaveText(codeStr) | ||||||
|  |  | ||||||
|     await click00r(-50, 0) |     await click00r(-50, 0) | ||||||
|     codeStr += `  |> line(${toSU([-50, 0])}, %)` |     codeStr += `  |> xLine(${toU(-50, 0)[0]}, %)` | ||||||
|     await expect(u.codeLocator).toHaveText(codeStr) |     await expect(u.codeLocator).toHaveText(codeStr) | ||||||
|  |  | ||||||
|     // exit the sketch, reset relative clicker |     // exit the sketch, reset relative clicker | ||||||
| @ -709,16 +708,18 @@ test.describe('Sketch tests', () => { | |||||||
|     codeStr += `  |> startProfileAt([2.03, 0], %)` |     codeStr += `  |> startProfileAt([2.03, 0], %)` | ||||||
|     await expect(u.codeLocator).toHaveText(codeStr) |     await expect(u.codeLocator).toHaveText(codeStr) | ||||||
|  |  | ||||||
|  |     // TODO: I couldn't use `toSU` here because of some rounding error causing | ||||||
|  |     // it to be off by 0.01 | ||||||
|     await click00r(30, 0) |     await click00r(30, 0) | ||||||
|     codeStr += `  |> line([2.04, 0], %)` |     codeStr += `  |> xLine(2.04, %)` | ||||||
|     await expect(u.codeLocator).toHaveText(codeStr) |     await expect(u.codeLocator).toHaveText(codeStr) | ||||||
|  |  | ||||||
|     await click00r(0, 30) |     await click00r(0, 30) | ||||||
|     codeStr += `  |> line([0, -2.03], %)` |     codeStr += `  |> yLine(-2.03, %)` | ||||||
|     await expect(u.codeLocator).toHaveText(codeStr) |     await expect(u.codeLocator).toHaveText(codeStr) | ||||||
|  |  | ||||||
|     await click00r(-30, 0) |     await click00r(-30, 0) | ||||||
|     codeStr += `  |> line([-2.04, 0], %)` |     codeStr += `  |> xLine(-2.04, %)` | ||||||
|     await expect(u.codeLocator).toHaveText(codeStr) |     await expect(u.codeLocator).toHaveText(codeStr) | ||||||
|  |  | ||||||
|     await click00r(undefined, undefined) |     await click00r(undefined, undefined) | ||||||
| @ -742,8 +743,8 @@ test.describe('Sketch tests', () => { | |||||||
|  |  | ||||||
|       const code = `sketch001 = startSketchOn('-XZ') |       const code = `sketch001 = startSketchOn('-XZ') | ||||||
|     |> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %) |     |> startProfileAt([${roundOff(scale * 69.6)}, ${roundOff(scale * 34.8)}], %) | ||||||
|     |> line([${roundOff(scale * 139.19)}, 0], %) |     |> xLine(${roundOff(scale * 139.19)}, %) | ||||||
|     |> line([0, -${roundOff(scale * 139.2)}], %) |     |> yLine(-${roundOff(scale * 139.2)}, %) | ||||||
|     |> lineTo([profileStartX(%), profileStartY(%)], %) |     |> lineTo([profileStartX(%), profileStartY(%)], %) | ||||||
|     |> close(%)` |     |> close(%)` | ||||||
|  |  | ||||||
|  | |||||||
| @ -462,7 +462,7 @@ test( | |||||||
|     await page.waitForTimeout(100) |     await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|     code += ` |     code += ` | ||||||
|   |> line([7.25, 0], %)` |   |> xLine(7.25, %)` | ||||||
|     await expect(page.locator('.cm-content')).toHaveText(code) |     await expect(page.locator('.cm-content')).toHaveText(code) | ||||||
|  |  | ||||||
|     await page |     await page | ||||||
| @ -647,7 +647,7 @@ test.describe( | |||||||
|       await page.waitForTimeout(100) |       await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|       code += ` |       code += ` | ||||||
|   |> line([7.25, 0], %)` |   |> xLine(7.25, %)` | ||||||
|       await expect(u.codeLocator).toHaveText(code) |       await expect(u.codeLocator).toHaveText(code) | ||||||
|  |  | ||||||
|       await page |       await page | ||||||
| @ -752,7 +752,7 @@ test.describe( | |||||||
|       await page.waitForTimeout(100) |       await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|       code += ` |       code += ` | ||||||
|   |> line([184.3, 0], %)` |   |> xLine(184.3, %)` | ||||||
|       await expect(u.codeLocator).toHaveText(code) |       await expect(u.codeLocator).toHaveText(code) | ||||||
|  |  | ||||||
|       await page |       await page | ||||||
| @ -1031,7 +1031,7 @@ test.describe('Grid visibility', { tag: '@snapshot' }, () => { | |||||||
|   }) |   }) | ||||||
| }) | }) | ||||||
|  |  | ||||||
| test('theme persists', async ({ page, context }) => { | test.fixme('theme persists', async ({ page, context }) => { | ||||||
|   const u = await getUtils(page) |   const u = await getUtils(page) | ||||||
|   await context.addInitScript(async () => { |   await context.addInitScript(async () => { | ||||||
|     localStorage.setItem( |     localStorage.setItem( | ||||||
|  | |||||||
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 52 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 46 KiB | 
| Before Width: | Height: | Size: 58 KiB After Width: | Height: | Size: 58 KiB | 
| Before Width: | Height: | Size: 55 KiB After Width: | Height: | Size: 55 KiB | 
| Before Width: | Height: | Size: 50 KiB After Width: | Height: | Size: 50 KiB | 
| Before Width: | Height: | Size: 47 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 44 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 43 KiB After Width: | Height: | Size: 44 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 41 KiB After Width: | Height: | Size: 41 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 33 KiB After Width: | Height: | Size: 33 KiB | 
| Before Width: | Height: | Size: 34 KiB After Width: | Height: | Size: 34 KiB | 
| Before Width: | Height: | Size: 52 KiB After Width: | Height: | Size: 53 KiB | 
| Before Width: | Height: | Size: 53 KiB After Width: | Height: | Size: 54 KiB | 
| Before Width: | Height: | Size: 74 KiB After Width: | Height: | Size: 74 KiB | 
| Before Width: | Height: | Size: 65 KiB After Width: | Height: | Size: 65 KiB | 
| Before Width: | Height: | Size: 49 KiB After Width: | Height: | Size: 49 KiB | 
| Before Width: | Height: | Size: 46 KiB After Width: | Height: | Size: 47 KiB | 
| Before Width: | Height: | Size: 64 KiB After Width: | Height: | Size: 65 KiB | 
| Before Width: | Height: | Size: 62 KiB After Width: | Height: | Size: 62 KiB | 
| Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB | 
| Before Width: | Height: | Size: 37 KiB After Width: | Height: | Size: 37 KiB | 
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 40 KiB After Width: | Height: | Size: 40 KiB | 
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 38 KiB After Width: | Height: | Size: 38 KiB | 
| Before Width: | Height: | Size: 39 KiB After Width: | Height: | Size: 39 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 42 KiB After Width: | Height: | Size: 42 KiB | 
| Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 36 KiB | 
| Before Width: | Height: | Size: 36 KiB After Width: | Height: | Size: 37 KiB | 
| @ -141,7 +141,7 @@ test.describe('Test network and connection issues', () => { | |||||||
|     await expect(page.locator('.cm-content')) |     await expect(page.locator('.cm-content')) | ||||||
|       .toHaveText(`sketch001 = startSketchOn('XZ') |       .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> startProfileAt(${commonPoints.startAt}, %) |     |> startProfileAt(${commonPoints.startAt}, %) | ||||||
|     |> line([${commonPoints.num1}, 0], %)`) |     |> xLine(${commonPoints.num1}, %)`) | ||||||
|  |  | ||||||
|     // Expect the network to be up |     // Expect the network to be up | ||||||
|     await expect(networkToggle).toContainText('Connected') |     await expect(networkToggle).toContainText('Connected') | ||||||
| @ -207,7 +207,7 @@ test.describe('Test network and connection issues', () => { | |||||||
|     await expect.poll(u.normalisedEditorCode) |     await expect.poll(u.normalisedEditorCode) | ||||||
|       .toBe(`sketch001 = startSketchOn('XZ') |       .toBe(`sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt([12.34, -12.34], %) |   |> startProfileAt([12.34, -12.34], %) | ||||||
|   |> line([12.34, 0], %) |   |> xLine(12.34, %) | ||||||
|   |> line([-12.34, 12.34], %) |   |> line([-12.34, 12.34], %) | ||||||
|  |  | ||||||
| `) | `) | ||||||
| @ -217,9 +217,9 @@ test.describe('Test network and connection issues', () => { | |||||||
|     await expect.poll(u.normalisedEditorCode) |     await expect.poll(u.normalisedEditorCode) | ||||||
|       .toBe(`sketch001 = startSketchOn('XZ') |       .toBe(`sketch001 = startSketchOn('XZ') | ||||||
|   |> startProfileAt([12.34, -12.34], %) |   |> startProfileAt([12.34, -12.34], %) | ||||||
|   |> line([12.34, 0], %) |   |> xLine(12.34, %) | ||||||
|   |> line([-12.34, 12.34], %) |   |> line([-12.34, 12.34], %) | ||||||
|   |> line([-12.34, 0], %) |   |> xLine(-12.34, %) | ||||||
|  |  | ||||||
| `) | `) | ||||||
|  |  | ||||||
|  | |||||||
| @ -45,7 +45,9 @@ export const commonPoints = { | |||||||
|   startAt: '[7.19, -9.7]', |   startAt: '[7.19, -9.7]', | ||||||
|   num1: 7.25, |   num1: 7.25, | ||||||
|   num2: 14.44, |   num2: 14.44, | ||||||
| } |   /** The Y-value of a common lineTo move we perform in tests */ | ||||||
|  |   num3: -2.44, | ||||||
|  | } as const | ||||||
|  |  | ||||||
| /** A semi-reliable color to check the default XZ plane on | /** A semi-reliable color to check the default XZ plane on | ||||||
|  * in dark mode in the default camera position |  * in dark mode in the default camera position | ||||||
| @ -118,15 +120,32 @@ async function waitForDefaultPlanesToBeVisible(page: Page) { | |||||||
|   ) |   ) | ||||||
| } | } | ||||||
|  |  | ||||||
| async function openPane(page: Page, testId: string) { | export async function checkIfPaneIsOpen(page: Page, testId: string) { | ||||||
|   const locator = page.getByTestId(testId) |   const paneButtonLocator = page.getByTestId(testId) | ||||||
|   await expect(locator).toBeVisible() |   await expect(paneButtonLocator).toBeVisible() | ||||||
|   const isOpen = (await locator?.getAttribute('aria-pressed')) === 'true' |   return (await paneButtonLocator?.getAttribute('aria-pressed')) === 'true' | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function openPane(page: Page, testId: string) { | ||||||
|  |   const paneButtonLocator = page.getByTestId(testId) | ||||||
|  |   await expect(paneButtonLocator).toBeVisible() | ||||||
|  |   const isOpen = await checkIfPaneIsOpen(page, testId) | ||||||
|  |  | ||||||
|   if (!isOpen) { |   if (!isOpen) { | ||||||
|     await locator.click() |     await paneButtonLocator.click() | ||||||
|     await expect(locator).toHaveAttribute('aria-pressed', 'true') |  | ||||||
|   } |   } | ||||||
|  |   await expect(paneButtonLocator).toHaveAttribute('aria-pressed', 'true') | ||||||
|  | } | ||||||
|  |  | ||||||
|  | export async function closePane(page: Page, testId: string) { | ||||||
|  |   const paneButtonLocator = page.getByTestId(testId) | ||||||
|  |   await expect(paneButtonLocator).toBeVisible() | ||||||
|  |   const isOpen = await checkIfPaneIsOpen(page, testId) | ||||||
|  |  | ||||||
|  |   if (isOpen) { | ||||||
|  |     await paneButtonLocator.click() | ||||||
|  |   } | ||||||
|  |   await expect(paneButtonLocator).toHaveAttribute('aria-pressed', 'false') | ||||||
| } | } | ||||||
|  |  | ||||||
| async function openKclCodePanel(page: Page) { | async function openKclCodePanel(page: Page) { | ||||||
| @ -286,7 +305,7 @@ export const getMovementUtils = (opts: any) => { | |||||||
|     return [last.x, last.y] |     return [last.x, last.y] | ||||||
|   } |   } | ||||||
|  |  | ||||||
|   return { toSU, click00r } |   return { toSU, toU, click00r } | ||||||
| } | } | ||||||
|  |  | ||||||
| async function waitForAuthAndLsp(page: Page) { | async function waitForAuthAndLsp(page: Page) { | ||||||
| @ -467,20 +486,6 @@ export async function getUtils(page: Page, test_?: typeof test) { | |||||||
|       return text.replace(/\s+/g, '') |       return text.replace(/\s+/g, '') | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
|     createAndSelectProject: async (hasText: string) => { |  | ||||||
|       return test_?.step( |  | ||||||
|         `Create and select project with text "${hasText}"`, |  | ||||||
|         async () => { |  | ||||||
|           // Without this, we get unreliable project creation. It's probably |  | ||||||
|           // due to a race between the FS being read and clicking doing something. |  | ||||||
|           await page.waitForTimeout(100) |  | ||||||
|           await page.getByTestId('home-new-file').click() |  | ||||||
|           const projectLinksPost = page.getByTestId('project-link') |  | ||||||
|           await projectLinksPost.filter({ hasText }).click() |  | ||||||
|         } |  | ||||||
|       ) |  | ||||||
|     }, |  | ||||||
|  |  | ||||||
|     editorTextMatches: async (code: string) => { |     editorTextMatches: async (code: string) => { | ||||||
|       const editor = page.locator(editorSelector) |       const editor = page.locator(editorSelector) | ||||||
|       return expect(editor).toHaveText(code, { useInnerText: true }) |       return expect(editor).toHaveText(code, { useInnerText: true }) | ||||||
| @ -520,6 +525,9 @@ export async function getUtils(page: Page, test_?: typeof test) { | |||||||
|           .locator('[data-testid="file-pane-scroll-container"] button') |           .locator('[data-testid="file-pane-scroll-container"] button') | ||||||
|           .filter({ hasText: name }) |           .filter({ hasText: name }) | ||||||
|           .click() |           .click() | ||||||
|  |         await expect(page.getByTestId('project-sidebar-toggle')).toContainText( | ||||||
|  |           name | ||||||
|  |         ) | ||||||
|       }) |       }) | ||||||
|     }, |     }, | ||||||
|  |  | ||||||
| @ -980,30 +988,25 @@ export async function isOutOfViewInScrollContainer( | |||||||
|   return isOutOfView |   return isOutOfView | ||||||
| } | } | ||||||
|  |  | ||||||
| export async function createProjectAndRenameIt({ | export async function createProject({ | ||||||
|   name, |   name, | ||||||
|   page, |   page, | ||||||
|  |   returnHome = false, | ||||||
| }: { | }: { | ||||||
|   name: string |   name: string | ||||||
|   page: Page |   page: Page | ||||||
|  |   returnHome?: boolean | ||||||
| }) { | }) { | ||||||
|   await page.getByRole('button', { name: 'New project' }).click() |   await test.step(`Create project and navigate to it`, async () => { | ||||||
|   await expect(page.getByText('Successfully created')).toBeVisible() |     await page.getByRole('button', { name: 'New project' }).click() | ||||||
|   await expect(page.getByText('Successfully created')).not.toBeVisible() |     await page.getByRole('textbox', { name: 'Name' }).fill(name) | ||||||
|  |     await page.getByRole('button', { name: 'Continue' }).click() | ||||||
|  |  | ||||||
|   await expect(page.getByText(`project-000`)).toBeVisible() |     if (returnHome) { | ||||||
|   await page.getByText(`project-000`).hover() |       await page.waitForURL('**/file/**', { waitUntil: 'domcontentloaded' }) | ||||||
|   await page.getByText(`project-000`).focus() |       await page.getByTestId('app-logo').click() | ||||||
|  |     } | ||||||
|   await page.getByLabel('sketch').first().click() |   }) | ||||||
|  |  | ||||||
|   await page.waitForTimeout(100) |  | ||||||
|  |  | ||||||
|   // type the name passed in |  | ||||||
|   await page.keyboard.press('Backspace') |  | ||||||
|   await page.keyboard.type(name) |  | ||||||
|  |  | ||||||
|   await page.getByLabel('checkmark').last().click() |  | ||||||
| } | } | ||||||
|  |  | ||||||
| export function executorInputPath(fileName: string): string { | export function executorInputPath(fileName: string): string { | ||||||
|  | |||||||
| @ -32,10 +32,17 @@ test.describe('Testing selections', () => { | |||||||
|       await u.waitForAuthSkipAppStart() |       await u.waitForAuthSkipAppStart() | ||||||
|       await u.openDebugPanel() |       await u.openDebugPanel() | ||||||
|  |  | ||||||
|       const xAxisClick = () => |       const yAxisClick = () => | ||||||
|         page.mouse.click(700, 253).then(() => page.waitForTimeout(100)) |         test.step('Click on Y axis', async () => { | ||||||
|  |           await page.mouse.move(600, 200, { steps: 5 }) | ||||||
|  |           await page.mouse.click(600, 200) | ||||||
|  |           await page.waitForTimeout(100) | ||||||
|  |         }) | ||||||
|       const xAxisClickAfterExitingSketch = () => |       const xAxisClickAfterExitingSketch = () => | ||||||
|         page.mouse.click(639, 278).then(() => page.waitForTimeout(100)) |         test.step(`Click on X axis after exiting sketch, which shifts it at the moment`, async () => { | ||||||
|  |           await page.mouse.click(639, 278) | ||||||
|  |           await page.waitForTimeout(100) | ||||||
|  |         }) | ||||||
|       const emptySpaceHover = () => |       const emptySpaceHover = () => | ||||||
|         test.step('Hover over empty space', async () => { |         test.step('Hover over empty space', async () => { | ||||||
|           await page.mouse.move(700, 143, { steps: 5 }) |           await page.mouse.move(700, 143, { steps: 5 }) | ||||||
| @ -80,23 +87,23 @@ test.describe('Testing selections', () => { | |||||||
|       await expect(page.locator('.cm-content')) |       await expect(page.locator('.cm-content')) | ||||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') |         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> startProfileAt(${commonPoints.startAt}, %) |     |> startProfileAt(${commonPoints.startAt}, %) | ||||||
|     |> line([${commonPoints.num1}, 0], %)`) |     |> xLine(${commonPoints.num1}, %)`) | ||||||
|  |  | ||||||
|       await page.waitForTimeout(100) |       await page.waitForTimeout(100) | ||||||
|       await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) |       await page.mouse.click(startXPx + PUR * 20, 500 - PUR * 20) | ||||||
|       await expect(page.locator('.cm-content')) |       await expect(page.locator('.cm-content')) | ||||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') |         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> startProfileAt(${commonPoints.startAt}, %) |     |> startProfileAt(${commonPoints.startAt}, %) | ||||||
|     |> line([${commonPoints.num1}, 0], %) |     |> xLine(${commonPoints.num1}, %) | ||||||
|     |> line([0, ${commonPoints.num1 + 0.01}], %)`) |     |> yLine(${commonPoints.num1 + 0.01}, %)`) | ||||||
|       await page.waitForTimeout(100) |       await page.waitForTimeout(100) | ||||||
|       await page.mouse.click(startXPx, 500 - PUR * 20) |       await page.mouse.click(startXPx, 500 - PUR * 20) | ||||||
|       await expect(page.locator('.cm-content')) |       await expect(page.locator('.cm-content')) | ||||||
|         .toHaveText(`sketch001 = startSketchOn('XZ') |         .toHaveText(`sketch001 = startSketchOn('XZ') | ||||||
|     |> startProfileAt(${commonPoints.startAt}, %) |     |> startProfileAt(${commonPoints.startAt}, %) | ||||||
|     |> line([${commonPoints.num1}, 0], %) |     |> xLine(${commonPoints.num1}, %) | ||||||
|     |> line([0, ${commonPoints.num1 + 0.01}], %) |     |> yLine(${commonPoints.num1 + 0.01}, %) | ||||||
|     |> line([-${commonPoints.num2}, 0], %)`) |     |> xLine(${commonPoints.num2 * -1}, %)`) | ||||||
|  |  | ||||||
|       // deselect line tool |       // deselect line tool | ||||||
|       await page.getByRole('button', { name: 'line Line', exact: true }).click() |       await page.getByRole('button', { name: 'line Line', exact: true }).click() | ||||||
| @ -121,51 +128,58 @@ test.describe('Testing selections', () => { | |||||||
|         // now check clicking works including axis |         // now check clicking works including axis | ||||||
|  |  | ||||||
|         // click a segment hold shift and click an axis, see that a relevant constraint is enabled |         // click a segment hold shift and click an axis, see that a relevant constraint is enabled | ||||||
|         await topHorzSegmentClick() |  | ||||||
|         await page.keyboard.down('Shift') |  | ||||||
|         const constrainButton = page.getByRole('button', { |         const constrainButton = page.getByRole('button', { | ||||||
|           name: 'Length: open menu', |           name: 'Length: open menu', | ||||||
|         }) |         }) | ||||||
|         const absYButton = page.getByRole('button', { name: 'Absolute Y' }) |         const absXButton = page.getByRole('button', { name: 'Absolute X' }) | ||||||
|         await constrainButton.click() |  | ||||||
|         await expect(absYButton).toBeDisabled() |         await test.step(`Select a segment and an axis, see that a relevant constraint is enabled`, async () => { | ||||||
|         await page.waitForTimeout(100) |           await topHorzSegmentClick() | ||||||
|         await xAxisClick() |           await page.keyboard.down('Shift') | ||||||
|         await page.keyboard.up('Shift') |           await constrainButton.click() | ||||||
|         await constrainButton.click() |           await expect(absXButton).toBeDisabled() | ||||||
|         await absYButton.and(page.locator(':not([disabled])')).waitFor() |           await page.waitForTimeout(100) | ||||||
|         await expect(absYButton).not.toBeDisabled() |           await yAxisClick() | ||||||
|  |           await page.keyboard.up('Shift') | ||||||
|  |           await constrainButton.click() | ||||||
|  |           await absXButton.and(page.locator(':not([disabled])')).waitFor() | ||||||
|  |           await expect(absXButton).not.toBeDisabled() | ||||||
|  |         }) | ||||||
|  |  | ||||||
|         // clear selection by clicking on nothing |  | ||||||
|         await emptySpaceClick() |         await emptySpaceClick() | ||||||
|  |  | ||||||
|         await page.waitForTimeout(100) |  | ||||||
|         // same selection but click the axis first |  | ||||||
|         await xAxisClick() |  | ||||||
|         await constrainButton.click() |  | ||||||
|         await expect(absYButton).toBeDisabled() |  | ||||||
|         await page.keyboard.down('Shift') |  | ||||||
|         await page.waitForTimeout(100) |  | ||||||
|         await topHorzSegmentClick() |  | ||||||
|         await page.waitForTimeout(100) |         await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|         await page.keyboard.up('Shift') |         await test.step(`Same selection but click the axis first`, async () => { | ||||||
|         await constrainButton.click() |           await yAxisClick() | ||||||
|         await expect(absYButton).not.toBeDisabled() |           await constrainButton.click() | ||||||
|  |           await expect(absXButton).toBeDisabled() | ||||||
|  |           await page.keyboard.down('Shift') | ||||||
|  |           await page.waitForTimeout(100) | ||||||
|  |           await topHorzSegmentClick() | ||||||
|  |           await page.waitForTimeout(100) | ||||||
|  |  | ||||||
|  |           await page.keyboard.up('Shift') | ||||||
|  |           await constrainButton.click() | ||||||
|  |           await expect(absXButton).not.toBeDisabled() | ||||||
|  |         }) | ||||||
|  |  | ||||||
|         // clear selection by clicking on nothing |         // clear selection by clicking on nothing | ||||||
|         await emptySpaceClick() |         await emptySpaceClick() | ||||||
|  |  | ||||||
|         // check the same selection again by putting cursor in code first then selecting axis |         // check the same selection again by putting cursor in code first then selecting axis | ||||||
|         await page.getByText(`  |> line([-${commonPoints.num2}, 0], %)`).click() |         await test.step(`Same selection but code selection then axis`, async () => { | ||||||
|         await page.keyboard.down('Shift') |           await page | ||||||
|         await constrainButton.click() |             .getByText(`  |> xLine(${commonPoints.num2 * -1}, %)`) | ||||||
|         await expect(absYButton).toBeDisabled() |             .click() | ||||||
|         await page.waitForTimeout(100) |           await page.keyboard.down('Shift') | ||||||
|         await xAxisClick() |           await constrainButton.click() | ||||||
|         await page.keyboard.up('Shift') |           await expect(absXButton).toBeDisabled() | ||||||
|         await constrainButton.click() |           await page.waitForTimeout(100) | ||||||
|         await expect(absYButton).not.toBeDisabled() |           await yAxisClick() | ||||||
|  |           await page.keyboard.up('Shift') | ||||||
|  |           await constrainButton.click() | ||||||
|  |           await expect(absXButton).not.toBeDisabled() | ||||||
|  |         }) | ||||||
|  |  | ||||||
|         // clear selection by clicking on nothing |         // clear selection by clicking on nothing | ||||||
|         await emptySpaceClick() |         await emptySpaceClick() | ||||||
| @ -180,7 +194,7 @@ test.describe('Testing selections', () => { | |||||||
|           process.platform === 'linux' ? 'Control' : 'Meta' |           process.platform === 'linux' ? 'Control' : 'Meta' | ||||||
|         ) |         ) | ||||||
|         await page.waitForTimeout(100) |         await page.waitForTimeout(100) | ||||||
|         await page.getByText(`  |> line([-${commonPoints.num2}, 0], %)`).click() |         await page.getByText(`  |> xLine(${commonPoints.num2 * -1}, %)`).click() | ||||||
|  |  | ||||||
|         await expect(page.locator('.cm-cursor')).toHaveCount(2) |         await expect(page.locator('.cm-cursor')).toHaveCount(2) | ||||||
|         await page.waitForTimeout(500) |         await page.waitForTimeout(500) | ||||||
| @ -924,6 +938,7 @@ sketch002 = startSketchOn(extrude001, $seg01) | |||||||
|     // test fillet button with the body in the scene |     // test fillet button with the body in the scene | ||||||
|     const codeToAdd = `${await u.codeLocator.allInnerTexts()} |     const codeToAdd = `${await u.codeLocator.allInnerTexts()} | ||||||
| extrude001 = extrude(10, sketch001)` | extrude001 = extrude(10, sketch001)` | ||||||
|  |     await u.codeLocator.clear() | ||||||
|     await u.codeLocator.fill(codeToAdd) |     await u.codeLocator.fill(codeToAdd) | ||||||
|     await selectSegment() |     await selectSegment() | ||||||
|     await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled() |     await expect(page.getByRole('button', { name: 'Fillet' })).toBeEnabled() | ||||||
|  | |||||||
| @ -7,6 +7,7 @@ import { | |||||||
|   setupElectron, |   setupElectron, | ||||||
|   tearDown, |   tearDown, | ||||||
|   executorInputPath, |   executorInputPath, | ||||||
|  |   createProject, | ||||||
| } from './test-utils' | } from './test-utils' | ||||||
| import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes' | import { SaveSettingsPayload, SettingsLevel } from 'lib/settings/settingsTypes' | ||||||
| import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants' | import { SETTINGS_FILE_NAME, PROJECT_SETTINGS_FILE_NAME } from 'lib/constants' | ||||||
| @ -257,7 +258,7 @@ test.describe('Testing settings', () => { | |||||||
|     }) |     }) | ||||||
|   }) |   }) | ||||||
|  |  | ||||||
|   test( |   test.fixme( | ||||||
|     `Project settings override user settings on desktop`, |     `Project settings override user settings on desktop`, | ||||||
|     { tag: ['@electron', '@skipWin'] }, |     { tag: ['@electron', '@skipWin'] }, | ||||||
|     async ({ browser: _ }, testInfo) => { |     async ({ browser: _ }, testInfo) => { | ||||||
| @ -265,10 +266,15 @@ test.describe('Testing settings', () => { | |||||||
|         process.platform === 'win32', |         process.platform === 'win32', | ||||||
|         'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557' |         'TODO: remove this skip https://github.com/KittyCAD/modeling-app/issues/3557' | ||||||
|       ) |       ) | ||||||
|       const { electronApp, page } = await setupElectron({ |       const projectName = 'bracket' | ||||||
|  |       const { | ||||||
|  |         electronApp, | ||||||
|  |         page, | ||||||
|  |         dir: projectDirName, | ||||||
|  |       } = await setupElectron({ | ||||||
|         testInfo, |         testInfo, | ||||||
|         folderSetupFn: async (dir) => { |         folderSetupFn: async (dir) => { | ||||||
|           const bracketDir = join(dir, 'bracket') |           const bracketDir = join(dir, projectName) | ||||||
|           await fsp.mkdir(bracketDir, { recursive: true }) |           await fsp.mkdir(bracketDir, { recursive: true }) | ||||||
|           await fsp.copyFile( |           await fsp.copyFile( | ||||||
|             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), |             executorInputPath('focusrite_scarlett_mounting_braket.kcl'), | ||||||
| @ -280,6 +286,12 @@ test.describe('Testing settings', () => { | |||||||
|       await page.setViewportSize({ width: 1200, height: 500 }) |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|       // Selectors and constants |       // Selectors and constants | ||||||
|  |       const tempProjectSettingsFilePath = join( | ||||||
|  |         projectDirName, | ||||||
|  |         projectName, | ||||||
|  |         PROJECT_SETTINGS_FILE_NAME | ||||||
|  |       ) | ||||||
|  |       const tempUserSettingsFilePath = join(projectDirName, SETTINGS_FILE_NAME) | ||||||
|       const userThemeColor = '120' |       const userThemeColor = '120' | ||||||
|       const projectThemeColor = '50' |       const projectThemeColor = '50' | ||||||
|       const settingsOpenButton = page.getByRole('link', { |       const settingsOpenButton = page.getByRole('link', { | ||||||
| @ -300,6 +312,12 @@ test.describe('Testing settings', () => { | |||||||
|         await themeColorSetting.fill(userThemeColor) |         await themeColorSetting.fill(userThemeColor) | ||||||
|         await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) |         await expect(logoLink).toHaveCSS('--primary-hue', userThemeColor) | ||||||
|         await settingsCloseButton.click() |         await settingsCloseButton.click() | ||||||
|  |         await expect | ||||||
|  |           .poll(async () => fsp.readFile(tempUserSettingsFilePath, 'utf-8'), { | ||||||
|  |             message: 'Setting should now be written to the file', | ||||||
|  |             timeout: 5_000, | ||||||
|  |           }) | ||||||
|  |           .toContain(`themeColor = "${userThemeColor}"`) | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await test.step('Set project theme color', async () => { |       await test.step('Set project theme color', async () => { | ||||||
| @ -311,13 +329,23 @@ test.describe('Testing settings', () => { | |||||||
|         await themeColorSetting.fill(projectThemeColor) |         await themeColorSetting.fill(projectThemeColor) | ||||||
|         await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor) |         await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor) | ||||||
|         await settingsCloseButton.click() |         await settingsCloseButton.click() | ||||||
|  |         // Make sure that the project settings file has been written to before continuing | ||||||
|  |         await expect | ||||||
|  |           .poll( | ||||||
|  |             async () => fsp.readFile(tempProjectSettingsFilePath, 'utf-8'), | ||||||
|  |             { | ||||||
|  |               message: 'Setting should now be written to the file', | ||||||
|  |               timeout: 5_000, | ||||||
|  |             } | ||||||
|  |           ) | ||||||
|  |           .toContain(`themeColor = "${projectThemeColor}"`) | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await test.step('Refresh the application and see project setting applied', async () => { |       await test.step('Refresh the application and see project setting applied', async () => { | ||||||
|         // Make sure we're done navigating before we reload |         // Make sure we're done navigating before we reload | ||||||
|         await expect(settingsCloseButton).not.toBeVisible() |         await expect(settingsCloseButton).not.toBeVisible() | ||||||
|         await page.reload({ waitUntil: 'domcontentloaded' }) |  | ||||||
|  |  | ||||||
|  |         await page.reload({ waitUntil: 'domcontentloaded' }) | ||||||
|         await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor) |         await expect(logoLink).toHaveCSS('--primary-hue', projectThemeColor) | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
| @ -386,7 +414,7 @@ test.describe('Testing settings', () => { | |||||||
|   ) |   ) | ||||||
|  |  | ||||||
|   // It was much easier to test the logo color than the background stream color. |   // It was much easier to test the logo color than the background stream color. | ||||||
|   test( |   test.fixme( | ||||||
|     'user settings reload on external change, on project and modeling view', |     'user settings reload on external change, on project and modeling view', | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ browserName }, testInfo) => { |     async ({ browserName }, testInfo) => { | ||||||
| @ -428,8 +456,7 @@ test.describe('Testing settings', () => { | |||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       await test.step('Check color of logo changed when in modeling view', async () => { |       await test.step('Check color of logo changed when in modeling view', async () => { | ||||||
|         await page.getByRole('button', { name: 'New project' }).click() |         await createProject({ name: 'project-000', page }) | ||||||
|         await page.getByTestId('project-link').first().click() |  | ||||||
|         await changeColor('58') |         await changeColor('58') | ||||||
|         await expect(logoLink).toHaveCSS('--primary-hue', '58') |         await expect(logoLink).toHaveCSS('--primary-hue', '58') | ||||||
|       }) |       }) | ||||||
| @ -447,7 +474,7 @@ test.describe('Testing settings', () => { | |||||||
|   test( |   test( | ||||||
|     'project settings reload on external change', |     'project settings reload on external change', | ||||||
|     { tag: '@electron' }, |     { tag: '@electron' }, | ||||||
|     async ({ browserName }, testInfo) => { |     async ({ browserName: _ }, testInfo) => { | ||||||
|       const { |       const { | ||||||
|         electronApp, |         electronApp, | ||||||
|         page, |         page, | ||||||
| @ -465,11 +492,7 @@ test.describe('Testing settings', () => { | |||||||
|         await expect(projectDirLink).toBeVisible() |         await expect(projectDirLink).toBeVisible() | ||||||
|       }) |       }) | ||||||
|  |  | ||||||
|       const projectLinks = page.getByTestId('project-link') |       await createProject({ name: 'project-000', page }) | ||||||
|       const oldCount = await projectLinks.count() |  | ||||||
|       await page.getByRole('button', { name: 'New project' }).click() |  | ||||||
|       await expect(projectLinks).toHaveCount(oldCount + 1) |  | ||||||
|       await projectLinks.filter({ hasText: 'project-000' }).first().click() |  | ||||||
|  |  | ||||||
|       const changeColorFs = async (color: string) => { |       const changeColorFs = async (color: string) => { | ||||||
|         const tempSettingsFilePath = join( |         const tempSettingsFilePath = join( | ||||||
|  | |||||||
| @ -1,5 +1,11 @@ | |||||||
| import { test, expect, Page } from '@playwright/test' | import { test, expect, Page } from '@playwright/test' | ||||||
| import { getUtils, setup, tearDown, setupElectron } from './test-utils' | import { | ||||||
|  |   getUtils, | ||||||
|  |   setup, | ||||||
|  |   tearDown, | ||||||
|  |   setupElectron, | ||||||
|  |   createProject, | ||||||
|  | } from './test-utils' | ||||||
| import { join } from 'path' | import { join } from 'path' | ||||||
| import fs from 'fs' | import fs from 'fs' | ||||||
|  |  | ||||||
| @ -700,17 +706,17 @@ test( | |||||||
|     const fileExists = () => |     const fileExists = () => | ||||||
|       fs.existsSync(join(dir, projectName, textToCadFileName)) |       fs.existsSync(join(dir, projectName, textToCadFileName)) | ||||||
|  |  | ||||||
|     const { |     const { openFilePanel, openKclCodePanel, waitForPageLoad } = await getUtils( | ||||||
|       createAndSelectProject, |       page, | ||||||
|       openFilePanel, |       test | ||||||
|       openKclCodePanel, |     ) | ||||||
|       waitForPageLoad, |  | ||||||
|     } = await getUtils(page, test) |  | ||||||
|  |  | ||||||
|     await page.setViewportSize({ width: 1200, height: 500 }) |     await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |  | ||||||
|     // Locators |     // Locators | ||||||
|     const projectMenuButton = page.getByRole('button', { name: projectName }) |     const projectMenuButton = page | ||||||
|  |       .getByTestId('project-sidebar-toggle') | ||||||
|  |       .filter({ hasText: projectName }) | ||||||
|     const textToCadFileButton = page.getByRole('listitem').filter({ |     const textToCadFileButton = page.getByRole('listitem').filter({ | ||||||
|       has: page.getByRole('button', { name: textToCadFileName }), |       has: page.getByRole('button', { name: textToCadFileName }), | ||||||
|     }) |     }) | ||||||
| @ -719,7 +725,7 @@ test( | |||||||
|     ) |     ) | ||||||
|  |  | ||||||
|     // Create and navigate to the project |     // Create and navigate to the project | ||||||
|     await createAndSelectProject('project-000') |     await createProject({ name: 'project-000', page }) | ||||||
|  |  | ||||||
|     // Wait for Start Sketch otherwise you will not have access Text-to-CAD command |     // Wait for Start Sketch otherwise you will not have access Text-to-CAD command | ||||||
|     await waitForPageLoad() |     await waitForPageLoad() | ||||||
|  | |||||||
| @ -256,181 +256,186 @@ test('First escape in tool pops you out of tool, second exits sketch mode', asyn | |||||||
|   ).not.toBeVisible() |   ).not.toBeVisible() | ||||||
| }) | }) | ||||||
|  |  | ||||||
| test('Basic default modeling and sketch hotkeys work', async ({ page }) => { | test.fixme( | ||||||
|   const u = await getUtils(page) |   'Basic default modeling and sketch hotkeys work', | ||||||
|  |   async ({ page }) => { | ||||||
|  |     const u = await getUtils(page) | ||||||
|  |  | ||||||
|   // This test can run long if it takes a little too long to load |     // This test can run long if it takes a little too long to load | ||||||
|   // the engine. |     // the engine. | ||||||
|   test.setTimeout(90000) |     test.setTimeout(90000) | ||||||
|   // This test has a weird bug on ubuntu |     // This test has a weird bug on ubuntu | ||||||
|   test.skip( |     // Funny, it's flaking on Windows too :). I think there is just something | ||||||
|     process.platform === 'linux', |     // actually wrong. | ||||||
|     'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444' |     test.skip( | ||||||
|   ) |       process.platform === 'linux', | ||||||
|   // Load the app with the code pane open |       'weird playwright bug on ubuntu https://github.com/KittyCAD/modeling-app/issues/2444' | ||||||
|  |     ) | ||||||
|  |     // Load the app with the code pane open | ||||||
|  |  | ||||||
|   await test.step(`Set up test`, async () => { |     await test.step(`Set up test`, async () => { | ||||||
|     await page.addInitScript(async () => { |       await page.addInitScript(async () => { | ||||||
|       localStorage.setItem( |         localStorage.setItem( | ||||||
|         'store', |           'store', | ||||||
|         JSON.stringify({ |           JSON.stringify({ | ||||||
|           state: { |             state: { | ||||||
|             openPanes: ['code'], |               openPanes: ['code'], | ||||||
|           }, |             }, | ||||||
|           version: 0, |             version: 0, | ||||||
|  |           }) | ||||||
|  |         ) | ||||||
|  |       }) | ||||||
|  |       await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  |       await u.waitForAuthSkipAppStart() | ||||||
|  |       await u.openDebugPanel() | ||||||
|  |       await u.expectCmdLog('[data-message-type="execution-done"]') | ||||||
|  |       await u.closeDebugPanel() | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     const codePane = page.locator('.cm-content') | ||||||
|  |     const lineButton = page.getByRole('button', { | ||||||
|  |       name: 'line Line', | ||||||
|  |       exact: true, | ||||||
|  |     }) | ||||||
|  |     const arcButton = page.getByRole('button', { | ||||||
|  |       name: 'arc Tangential Arc', | ||||||
|  |       exact: true, | ||||||
|  |     }) | ||||||
|  |     const extrudeButton = page.getByRole('button', { name: 'Extrude' }) | ||||||
|  |     const commandBarComboBox = page.getByPlaceholder('Search commands') | ||||||
|  |     const exitSketchButton = page.getByRole('button', { name: 'Exit Sketch' }) | ||||||
|  |  | ||||||
|  |     await test.step(`Type code with modeling hotkeys, shouldn't fire`, async () => { | ||||||
|  |       await codePane.click() | ||||||
|  |       await page.keyboard.type('//') | ||||||
|  |       await page.keyboard.press('s') | ||||||
|  |       await expect(commandBarComboBox).not.toBeVisible() | ||||||
|  |       await page.keyboard.press('e') | ||||||
|  |       await expect(commandBarComboBox).not.toBeVisible() | ||||||
|  |       await expect(codePane).toHaveText('//se') | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     // Blur focus from the code editor, use the s command to sketch | ||||||
|  |     await test.step(`Blur editor focus, enter sketch`, async () => { | ||||||
|  |       /** | ||||||
|  |        * TODO: There is a bug somewhere that causes this test to fail | ||||||
|  |        * if you toggle the codePane closed before your trigger the | ||||||
|  |        * start of the sketch. | ||||||
|  |        * and a separate Safari-only bug that causes the test to fail | ||||||
|  |        * if the pane is open the entire test. The maintainer of CodeMirror | ||||||
|  |        * has pinpointed this to the unusual browser behavior: | ||||||
|  |        * https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3 | ||||||
|  |        */ | ||||||
|  |       await blurCodeEditor() | ||||||
|  |       await page.waitForTimeout(1000) | ||||||
|  |       await page.keyboard.press('s') | ||||||
|  |       await page.waitForTimeout(1000) | ||||||
|  |       await page.mouse.move(800, 300, { steps: 5 }) | ||||||
|  |       await page.mouse.click(800, 300) | ||||||
|  |       await page.waitForTimeout(1000) | ||||||
|  |       await expect(lineButton).toHaveAttribute('aria-pressed', 'true', { | ||||||
|  |         timeout: 15_000, | ||||||
|  |       }) | ||||||
|  |     }) | ||||||
|  |  | ||||||
|  |     // Use some sketch hotkeys to create a sketch (l and a for now) | ||||||
|  |     await test.step(`Incomplete sketch with hotkeys`, async () => { | ||||||
|  |       await test.step(`Draw a line`, async () => { | ||||||
|  |         await page.mouse.move(700, 200, { steps: 5 }) | ||||||
|  |         await page.mouse.click(700, 200) | ||||||
|  |         await page.mouse.move(800, 250, { steps: 5 }) | ||||||
|  |         await page.mouse.click(800, 250) | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Unequip line tool`, async () => { | ||||||
|  |         await page.keyboard.press('l') | ||||||
|  |         await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true') | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Draw a tangential arc`, async () => { | ||||||
|  |         await page.keyboard.press('a') | ||||||
|  |         await expect(arcButton).toHaveAttribute('aria-pressed', 'true', { | ||||||
|  |           timeout: 10_000, | ||||||
|         }) |         }) | ||||||
|       ) |         await page.mouse.move(1000, 100, { steps: 5 }) | ||||||
|  |         await page.mouse.click(1000, 100) | ||||||
|  |       }) | ||||||
|  |  | ||||||
|  |       await test.step(`Unequip with escape, equip line tool`, async () => { | ||||||
|  |         await page.keyboard.press('Escape') | ||||||
|  |         await page.keyboard.press('l') | ||||||
|  |         await page.waitForTimeout(50) | ||||||
|  |         await expect(lineButton).toHaveAttribute('aria-pressed', 'true') | ||||||
|  |       }) | ||||||
|     }) |     }) | ||||||
|     await page.setViewportSize({ width: 1200, height: 500 }) |  | ||||||
|     await u.waitForAuthSkipAppStart() |  | ||||||
|     await u.openDebugPanel() |  | ||||||
|     await u.expectCmdLog('[data-message-type="execution-done"]') |  | ||||||
|     await u.closeDebugPanel() |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   const codePane = page.locator('.cm-content') |     await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => { | ||||||
|   const lineButton = page.getByRole('button', { |       // Since there's code now, we have to get to the end of the line | ||||||
|     name: 'line Line', |       await page.locator('.cm-line').last().click() | ||||||
|     exact: true, |       await page.keyboard.down('ControlOrMeta') | ||||||
|   }) |       await page.keyboard.press('ArrowRight') | ||||||
|   const arcButton = page.getByRole('button', { |       await page.keyboard.up('ControlOrMeta') | ||||||
|     name: 'arc Tangential Arc', |  | ||||||
|     exact: true, |  | ||||||
|   }) |  | ||||||
|   const extrudeButton = page.getByRole('button', { name: 'Extrude' }) |  | ||||||
|   const commandBarComboBox = page.getByPlaceholder('Search commands') |  | ||||||
|   const exitSketchButton = page.getByRole('button', { name: 'Exit Sketch' }) |  | ||||||
|  |  | ||||||
|   await test.step(`Type code with modeling hotkeys, shouldn't fire`, async () => { |       await page.keyboard.press('Enter') | ||||||
|     await codePane.click() |       await page.keyboard.type('//') | ||||||
|     await page.keyboard.type('//') |       await page.keyboard.press('l') | ||||||
|     await page.keyboard.press('s') |       await expect(lineButton).toHaveAttribute('aria-pressed', 'true') | ||||||
|     await expect(commandBarComboBox).not.toBeVisible() |       await page.keyboard.press('a') | ||||||
|     await page.keyboard.press('e') |       await expect(lineButton).toHaveAttribute('aria-pressed', 'true') | ||||||
|     await expect(commandBarComboBox).not.toBeVisible() |       await expect(codePane).toContainText('//la') | ||||||
|     await expect(codePane).toHaveText('//se') |       await page.keyboard.press('Backspace') | ||||||
|   }) |       await page.keyboard.press('Backspace') | ||||||
|  |       await page.keyboard.press('Backspace') | ||||||
|   // Blur focus from the code editor, use the s command to sketch |       await page.keyboard.press('Backspace') | ||||||
|   await test.step(`Blur editor focus, enter sketch`, async () => { |  | ||||||
|     /** |  | ||||||
|      * TODO: There is a bug somewhere that causes this test to fail |  | ||||||
|      * if you toggle the codePane closed before your trigger the |  | ||||||
|      * start of the sketch. |  | ||||||
|      * and a separate Safari-only bug that causes the test to fail |  | ||||||
|      * if the pane is open the entire test. The maintainer of CodeMirror |  | ||||||
|      * has pinpointed this to the unusual browser behavior: |  | ||||||
|      * https://discuss.codemirror.net/t/how-to-force-unfocus-of-the-codemirror-element-in-safari/8095/3 |  | ||||||
|      */ |  | ||||||
|     await blurCodeEditor() |  | ||||||
|     await page.waitForTimeout(1000) |  | ||||||
|     await page.keyboard.press('s') |  | ||||||
|     await page.waitForTimeout(1000) |  | ||||||
|     await page.mouse.move(800, 300, { steps: 5 }) |  | ||||||
|     await page.mouse.click(800, 300) |  | ||||||
|     await page.waitForTimeout(1000) |  | ||||||
|     await expect(lineButton).toHaveAttribute('aria-pressed', 'true', { |  | ||||||
|       timeout: 15_000, |  | ||||||
|     }) |     }) | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   // Use some sketch hotkeys to create a sketch (l and a for now) |     await test.step(`Close profile and exit sketch`, async () => { | ||||||
|   await test.step(`Incomplete sketch with hotkeys`, async () => { |       await blurCodeEditor() | ||||||
|     await test.step(`Draw a line`, async () => { |  | ||||||
|       await page.mouse.move(700, 200, { steps: 5 }) |       await page.mouse.move(700, 200, { steps: 5 }) | ||||||
|       await page.mouse.click(700, 200) |       await page.mouse.click(700, 200) | ||||||
|       await page.mouse.move(800, 250, { steps: 5 }) |       // On  close it will unequip the line tool. | ||||||
|       await page.mouse.click(800, 250) |       await expect(lineButton).toHaveAttribute('aria-pressed', 'false') | ||||||
|     }) |       await expect(exitSketchButton).toBeEnabled() | ||||||
|  |  | ||||||
|     await test.step(`Unequip line tool`, async () => { |  | ||||||
|       await page.keyboard.press('l') |  | ||||||
|       await expect(lineButton).not.toHaveAttribute('aria-pressed', 'true') |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await test.step(`Draw a tangential arc`, async () => { |  | ||||||
|       await page.keyboard.press('a') |  | ||||||
|       await expect(arcButton).toHaveAttribute('aria-pressed', 'true', { |  | ||||||
|         timeout: 10_000, |  | ||||||
|       }) |  | ||||||
|       await page.mouse.move(1000, 100, { steps: 5 }) |  | ||||||
|       await page.mouse.click(1000, 100) |  | ||||||
|     }) |  | ||||||
|  |  | ||||||
|     await test.step(`Unequip with escape, equip line tool`, async () => { |  | ||||||
|       await page.keyboard.press('Escape') |       await page.keyboard.press('Escape') | ||||||
|       await page.keyboard.press('l') |       await expect( | ||||||
|       await page.waitForTimeout(50) |         page.getByRole('button', { name: 'Exit Sketch' }) | ||||||
|       await expect(lineButton).toHaveAttribute('aria-pressed', 'true') |       ).not.toBeVisible() | ||||||
|     }) |     }) | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   await test.step(`Type code with sketch hotkeys, shouldn't fire`, async () => { |     // Extrude with e | ||||||
|     // Since there's code now, we have to get to the end of the line |     await test.step(`Extrude the sketch`, async () => { | ||||||
|     await page.locator('.cm-line').last().click() |       await page.mouse.click(750, 150) | ||||||
|     await page.keyboard.down('ControlOrMeta') |       await blurCodeEditor() | ||||||
|     await page.keyboard.press('ArrowRight') |       await expect(extrudeButton).toBeEnabled() | ||||||
|     await page.keyboard.up('ControlOrMeta') |       await page.keyboard.press('e') | ||||||
|  |       await page.waitForTimeout(500) | ||||||
|     await page.keyboard.press('Enter') |       await page.mouse.move(800, 200, { steps: 5 }) | ||||||
|     await page.keyboard.type('//') |       await page.mouse.click(800, 200) | ||||||
|     await page.keyboard.press('l') |       await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({ | ||||||
|     await expect(lineButton).toHaveAttribute('aria-pressed', 'true') |         timeout: 20_000, | ||||||
|     await page.keyboard.press('a') |       }) | ||||||
|     await expect(lineButton).toHaveAttribute('aria-pressed', 'true') |       await page.getByRole('button', { name: 'Continue' }).click() | ||||||
|     await expect(codePane).toContainText('//la') |       await expect( | ||||||
|     await page.keyboard.press('Backspace') |         page.getByRole('button', { name: 'Submit command' }) | ||||||
|     await page.keyboard.press('Backspace') |       ).toBeVisible() | ||||||
|     await page.keyboard.press('Backspace') |       await page.getByRole('button', { name: 'Submit command' }).click() | ||||||
|     await page.keyboard.press('Backspace') |       await expect(page.locator('.cm-content')).toContainText('extrude(') | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   await test.step(`Close profile and exit sketch`, async () => { |  | ||||||
|     await blurCodeEditor() |  | ||||||
|     await page.mouse.move(700, 200, { steps: 5 }) |  | ||||||
|     await page.mouse.click(700, 200) |  | ||||||
|     // On  close it will unequip the line tool. |  | ||||||
|     await expect(lineButton).toHaveAttribute('aria-pressed', 'false') |  | ||||||
|     await expect(exitSketchButton).toBeEnabled() |  | ||||||
|     await page.keyboard.press('Escape') |  | ||||||
|     await expect( |  | ||||||
|       page.getByRole('button', { name: 'Exit Sketch' }) |  | ||||||
|     ).not.toBeVisible() |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   // Extrude with e |  | ||||||
|   await test.step(`Extrude the sketch`, async () => { |  | ||||||
|     await page.mouse.click(750, 150) |  | ||||||
|     await blurCodeEditor() |  | ||||||
|     await expect(extrudeButton).toBeEnabled() |  | ||||||
|     await page.keyboard.press('e') |  | ||||||
|     await page.waitForTimeout(500) |  | ||||||
|     await page.mouse.move(800, 200, { steps: 5 }) |  | ||||||
|     await page.mouse.click(800, 200) |  | ||||||
|     await expect(page.getByRole('button', { name: 'Continue' })).toBeVisible({ |  | ||||||
|       timeout: 20_000, |  | ||||||
|     }) |     }) | ||||||
|     await page.getByRole('button', { name: 'Continue' }).click() |  | ||||||
|     await expect( |  | ||||||
|       page.getByRole('button', { name: 'Submit command' }) |  | ||||||
|     ).toBeVisible() |  | ||||||
|     await page.getByRole('button', { name: 'Submit command' }).click() |  | ||||||
|     await expect(page.locator('.cm-content')).toContainText('extrude(') |  | ||||||
|   }) |  | ||||||
|  |  | ||||||
|   // await codePaneButton.click() |     // await codePaneButton.click() | ||||||
|   // await expect(u.codeLocator).not.toBeVisible() |     // await expect(u.codeLocator).not.toBeVisible() | ||||||
|  |  | ||||||
|   /** |     /** | ||||||
|    * work-around: to stop `keyboard.press()` from typing in the editor even when it should be blurred |      * work-around: to stop `keyboard.press()` from typing in the editor even when it should be blurred | ||||||
|    */ |      */ | ||||||
|   async function blurCodeEditor() { |     async function blurCodeEditor() { | ||||||
|     await page.getByRole('button', { name: 'Commands' }).click() |       await page.getByRole('button', { name: 'Commands' }).click() | ||||||
|     await page.waitForTimeout(100) |       await page.waitForTimeout(100) | ||||||
|     await page.keyboard.press('Escape') |       await page.keyboard.press('Escape') | ||||||
|     await page.waitForTimeout(100) |       await page.waitForTimeout(100) | ||||||
|  |     } | ||||||
|   } |   } | ||||||
| }) | ) | ||||||
|  |  | ||||||
| test('Delete key does not navigate back', async ({ page }) => { | test('Delete key does not navigate back', async ({ page }) => { | ||||||
|   await page.setViewportSize({ width: 1200, height: 500 }) |   await page.setViewportSize({ width: 1200, height: 500 }) | ||||||
|  | |||||||
