@invarn/cibuild 2.9.5 → 2.9.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/chunk-35SI42CD.mjs +1 -0
- package/dist/chunk-IC45KVLJ.mjs +1 -0
- package/dist/chunk-XDY4V23V.mjs +1 -0
- package/dist/cli.cjs +14 -14
- package/dist/commands.mjs +1 -0
- package/dist/lib.mjs +3934 -0
- package/package.json +6 -10
- package/dist/src/cli.d.ts +0 -3
- package/dist/src/cli.d.ts.map +0 -1
- package/dist/src/cli.js +0 -399
- package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts +0 -38
- package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.d.ts.map +0 -1
- package/dist/src/commands/a-credential-target-follows-the-flavor-source-set.test.js +0 -222
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts +0 -41
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.d.ts.map +0 -1
- package/dist/src/commands/a-credential-two-workflows-cannot-share-is-declared-per-workflow.test.js +0 -324
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts +0 -2
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.d.ts.map +0 -1
- package/dist/src/commands/a-properties-stand-in-declares-the-keys-the-build-reads.test.js +0 -382
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts +0 -29
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.d.ts.map +0 -1
- package/dist/src/commands/an-already-stored-value-does-not-answer-the-target-question.test.js +0 -160
- package/dist/src/commands/android-gms-guard.test.d.ts +0 -9
- package/dist/src/commands/android-gms-guard.test.d.ts.map +0 -1
- package/dist/src/commands/android-gms-guard.test.js +0 -78
- package/dist/src/commands/android-java-version.test.d.ts +0 -12
- package/dist/src/commands/android-java-version.test.d.ts.map +0 -1
- package/dist/src/commands/android-java-version.test.js +0 -209
- package/dist/src/commands/android-scanner-java.test.d.ts +0 -2
- package/dist/src/commands/android-scanner-java.test.d.ts.map +0 -1
- package/dist/src/commands/android-scanner-java.test.js +0 -130
- package/dist/src/commands/android-scanner.d.ts +0 -250
- package/dist/src/commands/android-scanner.d.ts.map +0 -1
- package/dist/src/commands/android-scanner.js +0 -1639
- package/dist/src/commands/build.d.ts +0 -215
- package/dist/src/commands/build.d.ts.map +0 -1
- package/dist/src/commands/build.js +0 -1433
- package/dist/src/commands/build.test.d.ts +0 -13
- package/dist/src/commands/build.test.d.ts.map +0 -1
- package/dist/src/commands/build.test.js +0 -121
- package/dist/src/commands/detect-platform.d.ts +0 -13
- package/dist/src/commands/detect-platform.d.ts.map +0 -1
- package/dist/src/commands/detect-platform.js +0 -51
- package/dist/src/commands/detect-project.d.ts +0 -7
- package/dist/src/commands/detect-project.d.ts.map +0 -1
- package/dist/src/commands/detect-project.js +0 -12
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts +0 -26
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.d.ts.map +0 -1
- package/dist/src/commands/each-flavor-is-asked-for-its-own-properties-file.test.js +0 -196
- package/dist/src/commands/edit.d.ts +0 -12
- package/dist/src/commands/edit.d.ts.map +0 -1
- package/dist/src/commands/edit.js +0 -652
- package/dist/src/commands/edit.test.d.ts +0 -6
- package/dist/src/commands/edit.test.d.ts.map +0 -1
- package/dist/src/commands/edit.test.js +0 -45
- package/dist/src/commands/fidelity-trim.d.ts +0 -13
- package/dist/src/commands/fidelity-trim.d.ts.map +0 -1
- package/dist/src/commands/fidelity-trim.js +0 -21
- package/dist/src/commands/fidelity-trim.test.d.ts +0 -8
- package/dist/src/commands/fidelity-trim.test.d.ts.map +0 -1
- package/dist/src/commands/fidelity-trim.test.js +0 -79
- package/dist/src/commands/file-secret-collector.d.ts +0 -50
- package/dist/src/commands/file-secret-collector.d.ts.map +0 -1
- package/dist/src/commands/file-secret-collector.js +0 -217
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts +0 -38
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.d.ts.map +0 -1
- package/dist/src/commands/flavors-a-convention-plugin-creates-are-still-found.test.js +0 -300
- package/dist/src/commands/github-workflow.d.ts +0 -5
- package/dist/src/commands/github-workflow.d.ts.map +0 -1
- package/dist/src/commands/github-workflow.js +0 -45
- package/dist/src/commands/index.d.ts +0 -33
- package/dist/src/commands/index.d.ts.map +0 -1
- package/dist/src/commands/index.js +0 -33
- package/dist/src/commands/init.d.ts +0 -20
- package/dist/src/commands/init.d.ts.map +0 -1
- package/dist/src/commands/init.js +0 -264
- package/dist/src/commands/ios-scanner.d.ts +0 -104
- package/dist/src/commands/ios-scanner.d.ts.map +0 -1
- package/dist/src/commands/ios-scanner.js +0 -492
- package/dist/src/commands/ios-scheme-ranking.test.d.ts +0 -2
- package/dist/src/commands/ios-scheme-ranking.test.d.ts.map +0 -1
- package/dist/src/commands/ios-scheme-ranking.test.js +0 -264
- package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts +0 -30
- package/dist/src/commands/local-properties-reaches-every-workflow.test.d.ts.map +0 -1
- package/dist/src/commands/local-properties-reaches-every-workflow.test.js +0 -146
- package/dist/src/commands/reset.d.ts +0 -7
- package/dist/src/commands/reset.d.ts.map +0 -1
- package/dist/src/commands/reset.js +0 -81
- package/dist/src/commands/run.d.ts +0 -22
- package/dist/src/commands/run.d.ts.map +0 -1
- package/dist/src/commands/run.js +0 -141
- package/dist/src/commands/secrets-sync-workflow.d.ts +0 -15
- package/dist/src/commands/secrets-sync-workflow.d.ts.map +0 -1
- package/dist/src/commands/secrets-sync-workflow.js +0 -255
- package/dist/src/commands/secrets-upload.d.ts +0 -21
- package/dist/src/commands/secrets-upload.d.ts.map +0 -1
- package/dist/src/commands/secrets-upload.js +0 -177
- package/dist/src/commands/secrets-upload.test.d.ts +0 -5
- package/dist/src/commands/secrets-upload.test.d.ts.map +0 -1
- package/dist/src/commands/secrets-upload.test.js +0 -60
- package/dist/src/commands/validate.d.ts +0 -10
- package/dist/src/commands/validate.d.ts.map +0 -1
- package/dist/src/commands/validate.js +0 -46
- package/dist/src/config.d.ts +0 -3
- package/dist/src/config.d.ts.map +0 -1
- package/dist/src/config.js +0 -47
- package/dist/src/envman/cli.d.ts +0 -21
- package/dist/src/envman/cli.d.ts.map +0 -1
- package/dist/src/envman/cli.js +0 -240
- package/dist/src/envman/envman.d.ts +0 -83
- package/dist/src/envman/envman.d.ts.map +0 -1
- package/dist/src/envman/envman.js +0 -361
- package/dist/src/envman/envman.test.d.ts +0 -5
- package/dist/src/envman/envman.test.d.ts.map +0 -1
- package/dist/src/envman/envman.test.js +0 -236
- package/dist/src/envman/envstore-round-trip.test.d.ts +0 -27
- package/dist/src/envman/envstore-round-trip.test.d.ts.map +0 -1
- package/dist/src/envman/envstore-round-trip.test.js +0 -130
- package/dist/src/envman/index.d.ts +0 -23
- package/dist/src/envman/index.d.ts.map +0 -1
- package/dist/src/envman/index.js +0 -23
- package/dist/src/envman/shim.d.ts +0 -16
- package/dist/src/envman/shim.d.ts.map +0 -1
- package/dist/src/envman/shim.js +0 -66
- package/dist/src/envman/types.d.ts +0 -55
- package/dist/src/envman/types.d.ts.map +0 -1
- package/dist/src/envman/types.js +0 -12
- package/dist/src/lib.d.ts +0 -33
- package/dist/src/lib.d.ts.map +0 -1
- package/dist/src/lib.js +0 -37
- package/dist/src/pipeline.d.ts +0 -3
- package/dist/src/pipeline.d.ts.map +0 -1
- package/dist/src/pipeline.js +0 -57
- package/dist/src/runner-node-step.test.d.ts +0 -12
- package/dist/src/runner-node-step.test.d.ts.map +0 -1
- package/dist/src/runner-node-step.test.js +0 -61
- package/dist/src/runner.d.ts +0 -31
- package/dist/src/runner.d.ts.map +0 -1
- package/dist/src/runner.js +0 -240
- package/dist/src/shared/detect-project.d.ts +0 -57
- package/dist/src/shared/detect-project.d.ts.map +0 -1
- package/dist/src/shared/detect-project.js +0 -219
- package/dist/src/shared/detect-project.test.d.ts +0 -2
- package/dist/src/shared/detect-project.test.d.ts.map +0 -1
- package/dist/src/shared/detect-project.test.js +0 -161
- package/dist/src/shared/git.d.ts +0 -25
- package/dist/src/shared/git.d.ts.map +0 -1
- package/dist/src/shared/git.js +0 -53
- package/dist/src/shared/git.test.d.ts +0 -9
- package/dist/src/shared/git.test.d.ts.map +0 -1
- package/dist/src/shared/git.test.js +0 -66
- package/dist/src/shared/gitignore.d.ts +0 -6
- package/dist/src/shared/gitignore.d.ts.map +0 -1
- package/dist/src/shared/gitignore.js +0 -26
- package/dist/src/shared/gradle-settings.d.ts +0 -49
- package/dist/src/shared/gradle-settings.d.ts.map +0 -1
- package/dist/src/shared/gradle-settings.js +0 -138
- package/dist/src/shared/gradle-settings.test.d.ts +0 -2
- package/dist/src/shared/gradle-settings.test.d.ts.map +0 -1
- package/dist/src/shared/gradle-settings.test.js +0 -65
- package/dist/src/shared/prompts.d.ts +0 -30
- package/dist/src/shared/prompts.d.ts.map +0 -1
- package/dist/src/shared/prompts.js +0 -121
- package/dist/src/shared/xcode-container.test.d.ts +0 -2
- package/dist/src/shared/xcode-container.test.d.ts.map +0 -1
- package/dist/src/shared/xcode-container.test.js +0 -123
- package/dist/src/step-marker.d.ts +0 -12
- package/dist/src/step-marker.d.ts.map +0 -1
- package/dist/src/step-marker.js +0 -29
- package/dist/src/step-marker.test.d.ts +0 -11
- package/dist/src/step-marker.test.d.ts.map +0 -1
- package/dist/src/step-marker.test.js +0 -45
- package/dist/src/types.d.ts +0 -58
- package/dist/src/types.d.ts.map +0 -1
- package/dist/src/types.js +0 -2
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts +0 -28
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.d.ts.map +0 -1
- package/dist/src/yaml/a-cache-path-must-not-swallow-a-credential.test.js +0 -223
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts +0 -20
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.d.ts.map +0 -1
- package/dist/src/yaml/a-declared-secret-reaches-the-file-step.test.js +0 -201
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts +0 -37
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.d.ts.map +0 -1
- package/dist/src/yaml/a-declared-slot-may-name-the-workflow-it-belongs-to.test.js +0 -255
- package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts +0 -32
- package/dist/src/yaml/a-missing-credential-is-loud.test.d.ts.map +0 -1
- package/dist/src/yaml/a-missing-credential-is-loud.test.js +0 -301
- package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.d.ts +0 -18
- package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.d.ts.map +0 -1
- package/dist/src/yaml/a-runtime-requirement-names-a-real-provider.test.js +0 -66
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts +0 -34
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.d.ts.map +0 -1
- package/dist/src/yaml/a-scoped-slot-must-name-a-workflow-the-pipeline-has.test.js +0 -218
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts +0 -27
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.d.ts.map +0 -1
- package/dist/src/yaml/a-value-this-instance-stored-is-a-value-it-has.test.js +0 -126
- package/dist/src/yaml/bitrise-compat.d.ts +0 -65
- package/dist/src/yaml/bitrise-compat.d.ts.map +0 -1
- package/dist/src/yaml/bitrise-compat.js +0 -206
- package/dist/src/yaml/bitrise-compat.test.d.ts +0 -5
- package/dist/src/yaml/bitrise-compat.test.d.ts.map +0 -1
- package/dist/src/yaml/bitrise-compat.test.js +0 -347
- package/dist/src/yaml/cache-key-missing-file.test.d.ts +0 -12
- package/dist/src/yaml/cache-key-missing-file.test.d.ts.map +0 -1
- package/dist/src/yaml/cache-key-missing-file.test.js +0 -116
- package/dist/src/yaml/converter.d.ts +0 -103
- package/dist/src/yaml/converter.d.ts.map +0 -1
- package/dist/src/yaml/converter.js +0 -420
- package/dist/src/yaml/converter.test.d.ts +0 -5
- package/dist/src/yaml/converter.test.d.ts.map +0 -1
- package/dist/src/yaml/converter.test.js +0 -548
- package/dist/src/yaml/e2e.test.d.ts +0 -6
- package/dist/src/yaml/e2e.test.d.ts.map +0 -1
- package/dist/src/yaml/e2e.test.js +0 -446
- package/dist/src/yaml/env-resolver.d.ts +0 -218
- package/dist/src/yaml/env-resolver.d.ts.map +0 -1
- package/dist/src/yaml/env-resolver.js +0 -591
- package/dist/src/yaml/env-resolver.test.d.ts +0 -5
- package/dist/src/yaml/env-resolver.test.d.ts.map +0 -1
- package/dist/src/yaml/env-resolver.test.js +0 -502
- package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.d.ts +0 -16
- package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.d.ts.map +0 -1
- package/dist/src/yaml/every-build-log-witnesses-the-commit-on-disk.test.js +0 -126
- package/dist/src/yaml/fidelity-input-digest.d.ts +0 -37
- package/dist/src/yaml/fidelity-input-digest.d.ts.map +0 -1
- package/dist/src/yaml/fidelity-input-digest.js +0 -208
- package/dist/src/yaml/fidelity-input-digest.test.d.ts +0 -2
- package/dist/src/yaml/fidelity-input-digest.test.d.ts.map +0 -1
- package/dist/src/yaml/fidelity-input-digest.test.js +0 -167
- package/dist/src/yaml/fidelity-manifest.d.ts +0 -126
- package/dist/src/yaml/fidelity-manifest.d.ts.map +0 -1
- package/dist/src/yaml/fidelity-manifest.js +0 -134
- package/dist/src/yaml/fidelity-manifest.test.d.ts +0 -12
- package/dist/src/yaml/fidelity-manifest.test.d.ts.map +0 -1
- package/dist/src/yaml/fidelity-manifest.test.js +0 -251
- package/dist/src/yaml/interactive-prompts.d.ts +0 -71
- package/dist/src/yaml/interactive-prompts.d.ts.map +0 -1
- package/dist/src/yaml/interactive-prompts.js +0 -260
- package/dist/src/yaml/meta-helpers.d.ts +0 -28
- package/dist/src/yaml/meta-helpers.d.ts.map +0 -1
- package/dist/src/yaml/meta-helpers.js +0 -46
- package/dist/src/yaml/missing-env-handler.d.ts +0 -45
- package/dist/src/yaml/missing-env-handler.d.ts.map +0 -1
- package/dist/src/yaml/missing-env-handler.js +0 -64
- package/dist/src/yaml/parser.d.ts +0 -33
- package/dist/src/yaml/parser.d.ts.map +0 -1
- package/dist/src/yaml/parser.js +0 -148
- package/dist/src/yaml/parser.test.d.ts +0 -5
- package/dist/src/yaml/parser.test.d.ts.map +0 -1
- package/dist/src/yaml/parser.test.js +0 -83
- package/dist/src/yaml/pipeline-with-secrets.d.ts +0 -25
- package/dist/src/yaml/pipeline-with-secrets.d.ts.map +0 -1
- package/dist/src/yaml/pipeline-with-secrets.js +0 -76
- package/dist/src/yaml/platform-detector.d.ts +0 -83
- package/dist/src/yaml/platform-detector.d.ts.map +0 -1
- package/dist/src/yaml/platform-detector.js +0 -189
- package/dist/src/yaml/platform-detector.test.d.ts +0 -5
- package/dist/src/yaml/platform-detector.test.d.ts.map +0 -1
- package/dist/src/yaml/platform-detector.test.js +0 -525
- package/dist/src/yaml/preflight-validation.d.ts +0 -29
- package/dist/src/yaml/preflight-validation.d.ts.map +0 -1
- package/dist/src/yaml/preflight-validation.js +0 -21
- package/dist/src/yaml/secrets-manager.d.ts +0 -87
- package/dist/src/yaml/secrets-manager.d.ts.map +0 -1
- package/dist/src/yaml/secrets-manager.js +0 -230
- package/dist/src/yaml/shell-assignments.d.ts +0 -40
- package/dist/src/yaml/shell-assignments.d.ts.map +0 -1
- package/dist/src/yaml/shell-assignments.js +0 -77
- package/dist/src/yaml/step-validator-check-failure.test.d.ts +0 -24
- package/dist/src/yaml/step-validator-check-failure.test.d.ts.map +0 -1
- package/dist/src/yaml/step-validator-check-failure.test.js +0 -130
- package/dist/src/yaml/step-validator-self-assigned.test.d.ts +0 -23
- package/dist/src/yaml/step-validator-self-assigned.test.d.ts.map +0 -1
- package/dist/src/yaml/step-validator-self-assigned.test.js +0 -191
- package/dist/src/yaml/step-validator.d.ts +0 -67
- package/dist/src/yaml/step-validator.d.ts.map +0 -1
- package/dist/src/yaml/step-validator.js +0 -466
- package/dist/src/yaml/step-validator.test.d.ts +0 -18
- package/dist/src/yaml/step-validator.test.d.ts.map +0 -1
- package/dist/src/yaml/step-validator.test.js +0 -140
- package/dist/src/yaml/steps/android-sign.d.ts +0 -35
- package/dist/src/yaml/steps/android-sign.d.ts.map +0 -1
- package/dist/src/yaml/steps/android-sign.js +0 -147
- package/dist/src/yaml/steps/android-variant-resolution.test.d.ts +0 -2
- package/dist/src/yaml/steps/android-variant-resolution.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/android-variant-resolution.test.js +0 -260
- package/dist/src/yaml/steps/android-version.d.ts +0 -26
- package/dist/src/yaml/steps/android-version.d.ts.map +0 -1
- package/dist/src/yaml/steps/android-version.js +0 -128
- package/dist/src/yaml/steps/android-version.test.d.ts +0 -5
- package/dist/src/yaml/steps/android-version.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/android-version.test.js +0 -196
- package/dist/src/yaml/steps/android.d.ts +0 -156
- package/dist/src/yaml/steps/android.d.ts.map +0 -1
- package/dist/src/yaml/steps/android.js +0 -1092
- package/dist/src/yaml/steps/app-store-deploy.d.ts +0 -48
- package/dist/src/yaml/steps/app-store-deploy.d.ts.map +0 -1
- package/dist/src/yaml/steps/app-store-deploy.js +0 -167
- package/dist/src/yaml/steps/base.d.ts +0 -263
- package/dist/src/yaml/steps/base.d.ts.map +0 -1
- package/dist/src/yaml/steps/base.js +0 -380
- package/dist/src/yaml/steps/bitrise-android-tools.d.ts +0 -26
- package/dist/src/yaml/steps/bitrise-android-tools.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-android-tools.js +0 -200
- package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts +0 -5
- package/dist/src/yaml/steps/bitrise-android-tools.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-android-tools.test.js +0 -280
- package/dist/src/yaml/steps/bitrise-apk-info.d.ts +0 -22
- package/dist/src/yaml/steps/bitrise-apk-info.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-apk-info.js +0 -144
- package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts +0 -5
- package/dist/src/yaml/steps/bitrise-apk-info.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-apk-info.test.js +0 -331
- package/dist/src/yaml/steps/bitrise-slack.d.ts +0 -49
- package/dist/src/yaml/steps/bitrise-slack.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-slack.js +0 -280
- package/dist/src/yaml/steps/bitrise-slack.test.d.ts +0 -5
- package/dist/src/yaml/steps/bitrise-slack.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-slack.test.js +0 -484
- package/dist/src/yaml/steps/bitrise-ssh.d.ts +0 -27
- package/dist/src/yaml/steps/bitrise-ssh.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-ssh.js +0 -134
- package/dist/src/yaml/steps/bitrise-ssh.test.d.ts +0 -5
- package/dist/src/yaml/steps/bitrise-ssh.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/bitrise-ssh.test.js +0 -205
- package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts +0 -27
- package/dist/src/yaml/steps/cache-pull-daemon.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cache-pull-daemon.test.js +0 -212
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts +0 -42
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cache-pull-restore-roots.test.js +0 -209
- package/dist/src/yaml/steps/cache-push-daemon.test.d.ts +0 -27
- package/dist/src/yaml/steps/cache-push-daemon.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cache-push-daemon.test.js +0 -171
- package/dist/src/yaml/steps/cache.d.ts +0 -59
- package/dist/src/yaml/steps/cache.d.ts.map +0 -1
- package/dist/src/yaml/steps/cache.js +0 -1027
- package/dist/src/yaml/steps/capture-injection.d.ts +0 -30
- package/dist/src/yaml/steps/capture-injection.d.ts.map +0 -1
- package/dist/src/yaml/steps/capture-injection.js +0 -88
- package/dist/src/yaml/steps/capture-injection.test.d.ts +0 -2
- package/dist/src/yaml/steps/capture-injection.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/capture-injection.test.js +0 -163
- package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts +0 -42
- package/dist/src/yaml/steps/cocoapods-deployment-floor.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-deployment-floor.js +0 -140
- package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts +0 -11
- package/dist/src/yaml/steps/cocoapods-deployment-floor.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-deployment-floor.test.js +0 -176
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts +0 -22
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-install-emits-valid-bash.test.js +0 -97
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts +0 -17
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-locked-bundler.test.js +0 -282
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts +0 -17
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/cocoapods-skip-if-absent.test.js +0 -132
- package/dist/src/yaml/steps/envstore-loader.test.d.ts +0 -15
- package/dist/src/yaml/steps/envstore-loader.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/envstore-loader.test.js +0 -143
- package/dist/src/yaml/steps/fastlane.d.ts +0 -27
- package/dist/src/yaml/steps/fastlane.d.ts.map +0 -1
- package/dist/src/yaml/steps/fastlane.js +0 -79
- package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts +0 -53
- package/dist/src/yaml/steps/fidelity-aspect-guard.d.ts.map +0 -1
- package/dist/src/yaml/steps/fidelity-aspect-guard.js +0 -72
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts +0 -11
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/fidelity-aspect-guard.test.js +0 -50
- package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts +0 -87
- package/dist/src/yaml/steps/fidelity-reference-provenance.d.ts.map +0 -1
- package/dist/src/yaml/steps/fidelity-reference-provenance.js +0 -111
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts +0 -22
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/fidelity-reference-provenance.test.js +0 -118
- package/dist/src/yaml/steps/file.d.ts +0 -52
- package/dist/src/yaml/steps/file.d.ts.map +0 -1
- package/dist/src/yaml/steps/file.js +0 -108
- package/dist/src/yaml/steps/flutter.d.ts +0 -63
- package/dist/src/yaml/steps/flutter.d.ts.map +0 -1
- package/dist/src/yaml/steps/flutter.js +0 -215
- package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.d.ts +0 -16
- package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-clone-names-the-inputs-it-ignores.test.js +0 -127
- package/dist/src/yaml/steps/git-clone.d.ts +0 -32
- package/dist/src/yaml/steps/git-clone.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-clone.js +0 -171
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts +0 -10
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-fetch-retry-exec.test.js +0 -162
- package/dist/src/yaml/steps/git-fetch-retry.d.ts +0 -62
- package/dist/src/yaml/steps/git-fetch-retry.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-fetch-retry.js +0 -155
- package/dist/src/yaml/steps/git-fetch-retry.test.d.ts +0 -5
- package/dist/src/yaml/steps/git-fetch-retry.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/git-fetch-retry.test.js +0 -86
- package/dist/src/yaml/steps/google-play-deploy.d.ts +0 -37
- package/dist/src/yaml/steps/google-play-deploy.d.ts.map +0 -1
- package/dist/src/yaml/steps/google-play-deploy.js +0 -193
- package/dist/src/yaml/steps/google-play-deploy.test.d.ts +0 -5
- package/dist/src/yaml/steps/google-play-deploy.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/google-play-deploy.test.js +0 -310
- package/dist/src/yaml/steps/gradle-memory.d.ts +0 -61
- package/dist/src/yaml/steps/gradle-memory.d.ts.map +0 -1
- package/dist/src/yaml/steps/gradle-memory.js +0 -386
- package/dist/src/yaml/steps/gradle-memory.test.d.ts +0 -2
- package/dist/src/yaml/steps/gradle-memory.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/gradle-memory.test.js +0 -344
- package/dist/src/yaml/steps/gradle-sdk-cmake.d.ts +0 -28
- package/dist/src/yaml/steps/gradle-sdk-cmake.d.ts.map +0 -1
- package/dist/src/yaml/steps/gradle-sdk-cmake.js +0 -107
- package/dist/src/yaml/steps/gradle-sdk-cmake.test.d.ts +0 -2
- package/dist/src/yaml/steps/gradle-sdk-cmake.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/gradle-sdk-cmake.test.js +0 -183
- package/dist/src/yaml/steps/index.d.ts +0 -10
- package/dist/src/yaml/steps/index.d.ts.map +0 -1
- package/dist/src/yaml/steps/index.js +0 -1461
- package/dist/src/yaml/steps/ios-deps.d.ts +0 -78
- package/dist/src/yaml/steps/ios-deps.d.ts.map +0 -1
- package/dist/src/yaml/steps/ios-deps.js +0 -279
- package/dist/src/yaml/steps/ios-deps.test.d.ts +0 -5
- package/dist/src/yaml/steps/ios-deps.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ios-deps.test.js +0 -90
- package/dist/src/yaml/steps/ios-signing.d.ts +0 -31
- package/dist/src/yaml/steps/ios-signing.d.ts.map +0 -1
- package/dist/src/yaml/steps/ios-signing.js +0 -144
- package/dist/src/yaml/steps/ios-version.d.ts +0 -47
- package/dist/src/yaml/steps/ios-version.d.ts.map +0 -1
- package/dist/src/yaml/steps/ios-version.js +0 -151
- package/dist/src/yaml/steps/java-home-version-spec.test.d.ts +0 -10
- package/dist/src/yaml/steps/java-home-version-spec.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/java-home-version-spec.test.js +0 -58
- package/dist/src/yaml/steps/linting.d.ts +0 -47
- package/dist/src/yaml/steps/linting.d.ts.map +0 -1
- package/dist/src/yaml/steps/linting.js +0 -148
- package/dist/src/yaml/steps/phase2.test.d.ts +0 -6
- package/dist/src/yaml/steps/phase2.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/phase2.test.js +0 -197
- package/dist/src/yaml/steps/phase3.test.d.ts +0 -5
- package/dist/src/yaml/steps/phase3.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/phase3.test.js +0 -144
- package/dist/src/yaml/steps/phase4.test.d.ts +0 -5
- package/dist/src/yaml/steps/phase4.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/phase4.test.js +0 -166
- package/dist/src/yaml/steps/phase5.test.d.ts +0 -6
- package/dist/src/yaml/steps/phase5.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/phase5.test.js +0 -263
- package/dist/src/yaml/steps/registry.d.ts +0 -88
- package/dist/src/yaml/steps/registry.d.ts.map +0 -1
- package/dist/src/yaml/steps/registry.js +0 -125
- package/dist/src/yaml/steps/registry.test.d.ts +0 -5
- package/dist/src/yaml/steps/registry.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/registry.test.js +0 -235
- package/dist/src/yaml/steps/release.d.ts +0 -50
- package/dist/src/yaml/steps/release.d.ts.map +0 -1
- package/dist/src/yaml/steps/release.js +0 -154
- package/dist/src/yaml/steps/render-post-processor.d.ts +0 -100
- package/dist/src/yaml/steps/render-post-processor.d.ts.map +0 -1
- package/dist/src/yaml/steps/render-post-processor.js +0 -270
- package/dist/src/yaml/steps/render-post-processor.test.d.ts +0 -13
- package/dist/src/yaml/steps/render-post-processor.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/render-post-processor.test.js +0 -220
- package/dist/src/yaml/steps/script.d.ts +0 -23
- package/dist/src/yaml/steps/script.d.ts.map +0 -1
- package/dist/src/yaml/steps/script.js +0 -63
- package/dist/src/yaml/steps/spec-validation.test.d.ts +0 -6
- package/dist/src/yaml/steps/spec-validation.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/spec-validation.test.js +0 -130
- package/dist/src/yaml/steps/steps.test.d.ts +0 -6
- package/dist/src/yaml/steps/steps.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/steps.test.js +0 -881
- package/dist/src/yaml/steps/structural-facts.d.ts +0 -992
- package/dist/src/yaml/steps/structural-facts.d.ts.map +0 -1
- package/dist/src/yaml/steps/structural-facts.js +0 -3276
- package/dist/src/yaml/steps/structural-facts.test.d.ts +0 -12
- package/dist/src/yaml/steps/structural-facts.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/structural-facts.test.js +0 -3039
- package/dist/src/yaml/steps/test-config.d.ts +0 -5
- package/dist/src/yaml/steps/test-config.d.ts.map +0 -1
- package/dist/src/yaml/steps/test-config.js +0 -25
- package/dist/src/yaml/steps/trim-align-corpus.test.d.ts +0 -28
- package/dist/src/yaml/steps/trim-align-corpus.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/trim-align-corpus.test.js +0 -383
- package/dist/src/yaml/steps/trim-align.d.ts +0 -87
- package/dist/src/yaml/steps/trim-align.d.ts.map +0 -1
- package/dist/src/yaml/steps/trim-align.js +0 -367
- package/dist/src/yaml/steps/trim-align.test.d.ts +0 -11
- package/dist/src/yaml/steps/trim-align.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/trim-align.test.js +0 -261
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts +0 -66
- package/dist/src/yaml/steps/ui-fidelity-preview-android.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-preview-android.js +0 -251
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts +0 -13
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-preview-android.test.js +0 -302
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts +0 -134
- package/dist/src/yaml/steps/ui-fidelity-preview.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-preview.js +0 -173
- package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts +0 -11
- package/dist/src/yaml/steps/ui-fidelity-preview.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-preview.test.js +0 -210
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts +0 -155
- package/dist/src/yaml/steps/ui-fidelity-render-android.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.js +0 -1389
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts +0 -2
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-render-android.test.js +0 -1106
- package/dist/src/yaml/steps/ui-fidelity-render.d.ts +0 -230
- package/dist/src/yaml/steps/ui-fidelity-render.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-render.js +0 -1597
- package/dist/src/yaml/steps/ui-fidelity-render.test.d.ts +0 -18
- package/dist/src/yaml/steps/ui-fidelity-render.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/ui-fidelity-render.test.js +0 -1871
- package/dist/src/yaml/steps/xcode-app-product.d.ts +0 -90
- package/dist/src/yaml/steps/xcode-app-product.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-app-product.js +0 -247
- package/dist/src/yaml/steps/xcode-app-product.test.d.ts +0 -2
- package/dist/src/yaml/steps/xcode-app-product.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-app-product.test.js +0 -202
- package/dist/src/yaml/steps/xcode-derived-data.test.d.ts +0 -30
- package/dist/src/yaml/steps/xcode-derived-data.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-derived-data.test.js +0 -279
- package/dist/src/yaml/steps/xcode-destination.d.ts +0 -47
- package/dist/src/yaml/steps/xcode-destination.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-destination.js +0 -202
- package/dist/src/yaml/steps/xcode-destination.test.d.ts +0 -9
- package/dist/src/yaml/steps/xcode-destination.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-destination.test.js +0 -227
- package/dist/src/yaml/steps/xcode-new.test.d.ts +0 -5
- package/dist/src/yaml/steps/xcode-new.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-new.test.js +0 -211
- package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts +0 -9
- package/dist/src/yaml/steps/xcode-resolve-retry.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-resolve-retry.test.js +0 -146
- package/dist/src/yaml/steps/xcode-simulator-archs.d.ts +0 -53
- package/dist/src/yaml/steps/xcode-simulator-archs.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-simulator-archs.js +0 -98
- package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts +0 -21
- package/dist/src/yaml/steps/xcode-simulator-archs.test.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode-simulator-archs.test.js +0 -155
- package/dist/src/yaml/steps/xcode.d.ts +0 -230
- package/dist/src/yaml/steps/xcode.d.ts.map +0 -1
- package/dist/src/yaml/steps/xcode.js +0 -1350
- package/dist/src/yaml/types.d.ts +0 -76
- package/dist/src/yaml/types.d.ts.map +0 -1
- package/dist/src/yaml/types.js +0 -5
- package/dist/src/yaml/validation-types.d.ts +0 -102
- package/dist/src/yaml/validation-types.d.ts.map +0 -1
- package/dist/src/yaml/validation-types.js +0 -8
- package/dist/src/yaml/yaml-updater.d.ts +0 -24
- package/dist/src/yaml/yaml-updater.d.ts.map +0 -1
- package/dist/src/yaml/yaml-updater.js +0 -128
|
@@ -1,1871 +0,0 @@
|
|
|
1
|
-
/**
|
|
2
|
-
* Tests for the ui-fidelity-render step.
|
|
3
|
-
*
|
|
4
|
-
* The runtime tests execute the generated node script for real inside a temp
|
|
5
|
-
* project directory, with a PATH-shimmed fake `swift` binary so harness
|
|
6
|
-
* builds and renders are deterministic. Fake swift behavior is driven by env
|
|
7
|
-
* vars (FAKE_SWIFT_PROBE_FAIL, FAKE_SWIFT_COMPILE_FAIL, FAKE_SWIFT_RUN_FAIL,
|
|
8
|
-
* FAKE_SWIFT_RUN_SILENT). Like the real toolchain, its failure diagnostics
|
|
9
|
-
* embed the absolute harness package path, so the suite can prove those
|
|
10
|
-
* paths never reach protocol-result.json.
|
|
11
|
-
*
|
|
12
|
-
* The committed SwiftPM fixture (test/fixtures/ui-fidelity-package) is wired
|
|
13
|
-
* through the harness with the fake toolchain in every run; set
|
|
14
|
-
* CIBUILD_UI_FIDELITY_REAL_SWIFT=1 to also build and render it with the real
|
|
15
|
-
* Swift toolchain (macOS with Xcode required, slow).
|
|
16
|
-
*/
|
|
17
|
-
import { describe, test, expect, afterAll } from '@jest/globals';
|
|
18
|
-
import { spawn, spawnSync } from 'node:child_process';
|
|
19
|
-
import { gzipSync } from 'node:zlib';
|
|
20
|
-
import { chmodSync, existsSync, mkdirSync, mkdtempSync, readdirSync, readFileSync, realpathSync, rmSync, writeFileSync, } from 'node:fs';
|
|
21
|
-
import { tmpdir } from 'node:os';
|
|
22
|
-
import { dirname, join, resolve } from 'node:path';
|
|
23
|
-
import { fileURLToPath } from 'node:url';
|
|
24
|
-
import { DEFAULT_RENDER_SIZE, DEFAULT_SCALE, UiFidelityRenderStepExecutor, generateRenderScript, getRenderScriptInternals, parseRenderSize, parseScale, } from './ui-fidelity-render.js';
|
|
25
|
-
import { clearRegistry, getStepMetadata, hasStep } from './registry.js';
|
|
26
|
-
import { initializeStepRegistry } from './index.js';
|
|
27
|
-
import { testConfig } from './test-config.js';
|
|
28
|
-
const PNG_MAGIC = Buffer.from([0x89, 0x50, 0x4e, 0x47, 0x0d, 0x0a, 0x1a, 0x0a]);
|
|
29
|
-
/** The committed SwiftPM fixture used by examples/ui-fidelity-render.yml. */
|
|
30
|
-
const FIXTURE_PACKAGE = resolve(dirname(fileURLToPath(import.meta.url)), '../../../test/fixtures/ui-fidelity-package');
|
|
31
|
-
/**
|
|
32
|
-
* Fake `swift` CLI. Mirrors the exact invocations the render script makes:
|
|
33
|
-
* swift package --package-path <dir> dump-package
|
|
34
|
-
* swift build --package-path <dir> --target RenderProbe
|
|
35
|
-
* swift build --package-path <dir> --product Render_<Screen>
|
|
36
|
-
* swift run --package-path <dir> --skip-build Render_<Screen> <output.png>
|
|
37
|
-
* dump-package output is driven by FAKE_SWIFT_DUMP_PRODUCTS (comma-separated
|
|
38
|
-
* library product names; unset = "FixtureViews", empty = no products) and
|
|
39
|
-
* FAKE_SWIFT_DUMP_FAIL.
|
|
40
|
-
*/
|
|
41
|
-
const FAKE_SWIFT_LINES = [
|
|
42
|
-
'#!/bin/bash',
|
|
43
|
-
'[ -n "$FAKE_SWIFT_LOG" ] && echo "$*" >> "$FAKE_SWIFT_LOG"',
|
|
44
|
-
'cmd="$1"; shift',
|
|
45
|
-
'case "$cmd" in',
|
|
46
|
-
' package)',
|
|
47
|
-
' if [ -n "$FAKE_SWIFT_DUMP_FAIL" ]; then',
|
|
48
|
-
' echo "error: malformed Package.swift manifest" >&2',
|
|
49
|
-
' exit 1',
|
|
50
|
-
' fi',
|
|
51
|
-
' products="${FAKE_SWIFT_DUMP_PRODUCTS-FixtureViews}"',
|
|
52
|
-
' json=""',
|
|
53
|
-
' IFS=","',
|
|
54
|
-
' for p in $products; do',
|
|
55
|
-
' [ -n "$json" ] && json="$json,"',
|
|
56
|
-
' json="$json{\\"name\\":\\"$p\\",\\"type\\":{\\"library\\":[\\"automatic\\"]}}"',
|
|
57
|
-
' done',
|
|
58
|
-
' printf \'{"name":"shipped-package","products":[%s]}\\n\' "$json"',
|
|
59
|
-
' exit 0 ;;',
|
|
60
|
-
' build)',
|
|
61
|
-
' product=""; target=""; pkg=""; showbin=""',
|
|
62
|
-
' while [ $# -gt 0 ]; do',
|
|
63
|
-
' case "$1" in',
|
|
64
|
-
' --product) product="$2"; shift 2 ;;',
|
|
65
|
-
' --target) target="$2"; shift 2 ;;',
|
|
66
|
-
' --package-path) pkg="$2"; shift 2 ;;',
|
|
67
|
-
' --show-bin-path) showbin=1; shift ;;',
|
|
68
|
-
' *) shift ;;',
|
|
69
|
-
' esac',
|
|
70
|
-
' done',
|
|
71
|
-
' if [ -n "$showbin" ]; then',
|
|
72
|
-
' bp="$pkg/.build/debug"',
|
|
73
|
-
' [ -n "$FAKE_SWIFT_MODULE_CATALOG" ] && mkdir -p "$bp/$FAKE_SWIFT_MODULE_CATALOG.bundle/Media.xcassets"',
|
|
74
|
-
' echo "$bp"; exit 0',
|
|
75
|
-
' fi',
|
|
76
|
-
' name="${product:-$target}"',
|
|
77
|
-
' if [ "$name" = "RenderProbe" ]; then',
|
|
78
|
-
' if [ -n "$FAKE_SWIFT_PROBE_FAIL" ]; then',
|
|
79
|
-
' echo "$pkg/Sources/RenderProbe/Probe.swift:1:8: error: could not resolve package dependencies" >&2',
|
|
80
|
-
' exit 1',
|
|
81
|
-
' fi',
|
|
82
|
-
' exit 0',
|
|
83
|
-
' fi',
|
|
84
|
-
' screen="${name#Render_}"',
|
|
85
|
-
' case ",$FAKE_SWIFT_COMPILE_FAIL," in',
|
|
86
|
-
' *",$screen,"*)',
|
|
87
|
-
' echo "$pkg/Sources/Render_$screen/Render.swift:7:20: error: cannot find $screen in scope" >&2',
|
|
88
|
-
' exit 1 ;;',
|
|
89
|
-
' esac',
|
|
90
|
-
' exit 0 ;;',
|
|
91
|
-
' run)',
|
|
92
|
-
' pos=()',
|
|
93
|
-
' while [ $# -gt 0 ]; do',
|
|
94
|
-
' case "$1" in',
|
|
95
|
-
' --package-path) shift 2 ;;',
|
|
96
|
-
' --skip-build) shift ;;',
|
|
97
|
-
' *) pos+=("$1"); shift ;;',
|
|
98
|
-
' esac',
|
|
99
|
-
' done',
|
|
100
|
-
' name="${pos[0]}"; out="${pos[1]}"',
|
|
101
|
-
' screen="${name#Render_}"',
|
|
102
|
-
' case ",$FAKE_SWIFT_RUN_FAIL," in',
|
|
103
|
-
' *",$screen,"*)',
|
|
104
|
-
' echo "Fatal error: render failed for $screen" >&2',
|
|
105
|
-
' exit 1 ;;',
|
|
106
|
-
' esac',
|
|
107
|
-
' case ",$FAKE_SWIFT_RUN_SILENT," in',
|
|
108
|
-
' *",$screen,"*)',
|
|
109
|
-
' exit 0 ;;',
|
|
110
|
-
' esac',
|
|
111
|
-
" printf '\\x89PNG\\r\\n\\x1a\\n' > \"$out\"",
|
|
112
|
-
' printf \'fake-render:%s\' "$screen" >> "$out"',
|
|
113
|
-
' exit 0 ;;',
|
|
114
|
-
' *)',
|
|
115
|
-
' exit 0 ;;',
|
|
116
|
-
'esac',
|
|
117
|
-
];
|
|
118
|
-
/**
|
|
119
|
-
* PATH-shimmed fake `ci`, providing only the branch the shared trim stage
|
|
120
|
-
* uses: `ci fidelity-trim <in> <out> <aligned> <ref>`. Overwrites <out> with a
|
|
121
|
-
* PNG marked "trimmed:<in basename>" and writes an "aligned:<in basename>" PNG
|
|
122
|
-
* so tests can prove both outputs. Emits a fixed TRIMDIMS line (rendered
|
|
123
|
-
* 320x640, reference 160x320, source 400x800). FAKE_TRIM_FAIL exercises
|
|
124
|
-
* warn-and-continue.
|
|
125
|
-
*/
|
|
126
|
-
const FAKE_CI_LINES = [
|
|
127
|
-
'#!/bin/bash',
|
|
128
|
-
'case "$1" in',
|
|
129
|
-
' fidelity-trim)',
|
|
130
|
-
' if [ -n "$FAKE_TRIM_FAIL" ]; then echo "trim: simulated failure" >&2; exit 1; fi',
|
|
131
|
-
' in_png="$2"; out_png="$3"; aligned_png="$4"',
|
|
132
|
-
" printf '\\x89PNG\\r\\n\\x1a\\n' > \"$out_png\"",
|
|
133
|
-
' printf \'trimmed:%s\' "$(basename "$in_png")" >> "$out_png"',
|
|
134
|
-
' if [ -n "$aligned_png" ]; then',
|
|
135
|
-
" printf '\\x89PNG\\r\\n\\x1a\\n' > \"$aligned_png\"",
|
|
136
|
-
' printf \'aligned:%s\' "$(basename "$in_png")" >> "$aligned_png"',
|
|
137
|
-
' fi',
|
|
138
|
-
' echo "TRIMDIMS 320 640 160 320 400 800"',
|
|
139
|
-
' exit 0 ;;',
|
|
140
|
-
' *) exit 0 ;;',
|
|
141
|
-
'esac',
|
|
142
|
-
];
|
|
143
|
-
/**
|
|
144
|
-
* PATH-shimmed fake `xcrun` covering `xcrun actool`. Logs its args to
|
|
145
|
-
* $FAKE_XCRUN_LOG, writes a stub Assets.car into the --compile directory,
|
|
146
|
-
* and fails (exit 1) when FAKE_ACTOOL_FAIL is set so the warn-and-continue
|
|
147
|
-
* path is exercisable.
|
|
148
|
-
*/
|
|
149
|
-
const FAKE_XCRUN_LINES = [
|
|
150
|
-
'#!/bin/bash',
|
|
151
|
-
'[ -n "$FAKE_XCRUN_LOG" ] && echo "$*" >> "$FAKE_XCRUN_LOG"',
|
|
152
|
-
'sub="$1"; shift',
|
|
153
|
-
'case "$sub" in',
|
|
154
|
-
' actool)',
|
|
155
|
-
' out=""',
|
|
156
|
-
' while [ $# -gt 0 ]; do',
|
|
157
|
-
' case "$1" in',
|
|
158
|
-
' --compile) out="$2"; shift 2 ;;',
|
|
159
|
-
' *) shift ;;',
|
|
160
|
-
' esac',
|
|
161
|
-
' done',
|
|
162
|
-
' if [ -n "$FAKE_ACTOOL_FAIL" ]; then',
|
|
163
|
-
' echo "error: actool: malformed asset catalog" >&2',
|
|
164
|
-
' exit 1',
|
|
165
|
-
' fi',
|
|
166
|
-
' [ -n "$out" ] && mkdir -p "$out" && printf \'fake-car\' > "$out/Assets.car"',
|
|
167
|
-
' exit 0 ;;',
|
|
168
|
-
' *)',
|
|
169
|
-
' exit 0 ;;',
|
|
170
|
-
'esac',
|
|
171
|
-
];
|
|
172
|
-
const tempDirs = [];
|
|
173
|
-
afterAll(() => {
|
|
174
|
-
for (const dir of tempDirs) {
|
|
175
|
-
try {
|
|
176
|
-
rmSync(dir, { recursive: true, force: true });
|
|
177
|
-
}
|
|
178
|
-
catch {
|
|
179
|
-
// best effort
|
|
180
|
-
}
|
|
181
|
-
}
|
|
182
|
-
});
|
|
183
|
-
function makeProject(params) {
|
|
184
|
-
const dir = mkdtempSync(join(tmpdir(), 'ui-fidelity-test-'));
|
|
185
|
-
tempDirs.push(dir);
|
|
186
|
-
mkdirSync(join(dir, '.ci', 'inputs'), { recursive: true });
|
|
187
|
-
const packageDir = join(dir, 'user-package');
|
|
188
|
-
mkdirSync(packageDir, { recursive: true });
|
|
189
|
-
const binDir = join(dir, 'bin');
|
|
190
|
-
mkdirSync(binDir, { recursive: true });
|
|
191
|
-
const swiftPath = join(binDir, 'swift');
|
|
192
|
-
writeFileSync(swiftPath, FAKE_SWIFT_LINES.join('\n') + '\n');
|
|
193
|
-
chmodSync(swiftPath, 0o755);
|
|
194
|
-
const xcrunPath = join(binDir, 'xcrun');
|
|
195
|
-
writeFileSync(xcrunPath, FAKE_XCRUN_LINES.join('\n') + '\n');
|
|
196
|
-
chmodSync(xcrunPath, 0o755);
|
|
197
|
-
const ciPath = join(binDir, 'ci');
|
|
198
|
-
writeFileSync(ciPath, FAKE_CI_LINES.join('\n') + '\n');
|
|
199
|
-
chmodSync(ciPath, 0o755);
|
|
200
|
-
if (params !== undefined) {
|
|
201
|
-
writeFileSync(join(dir, '.ci', 'inputs', 'params.json'), typeof params === 'string' ? params : JSON.stringify(params));
|
|
202
|
-
}
|
|
203
|
-
return { dir, packageDir, binDir };
|
|
204
|
-
}
|
|
205
|
-
function writeReference(project, basename, label = basename) {
|
|
206
|
-
writeFileSync(join(project.dir, '.ci', 'inputs', basename), Buffer.concat([PNG_MAGIC, Buffer.from('ref:' + label)]));
|
|
207
|
-
}
|
|
208
|
-
async function buildScript(project, inputs = {}) {
|
|
209
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
210
|
-
const step = await executor.execute({ package_path: project.packageDir, target: 'FixtureViews', ...inputs }, {}, testConfig);
|
|
211
|
-
return step.script;
|
|
212
|
-
}
|
|
213
|
-
function runScript(project, script, env = {}) {
|
|
214
|
-
return spawnSync('node', ['-e', script], {
|
|
215
|
-
cwd: project.dir,
|
|
216
|
-
encoding: 'utf-8',
|
|
217
|
-
env: {
|
|
218
|
-
...process.env,
|
|
219
|
-
PATH: project.binDir + ':' + (process.env.PATH ?? ''),
|
|
220
|
-
...env,
|
|
221
|
-
},
|
|
222
|
-
});
|
|
223
|
-
}
|
|
224
|
-
function tarHeader(name, size, typeflag) {
|
|
225
|
-
const header = Buffer.alloc(512);
|
|
226
|
-
header.write(name, 0, 100, 'utf-8');
|
|
227
|
-
header.write('0000755\0', 100, 8, 'ascii'); // mode
|
|
228
|
-
header.write('0000000\0', 108, 8, 'ascii'); // uid
|
|
229
|
-
header.write('0000000\0', 116, 8, 'ascii'); // gid
|
|
230
|
-
header.write(size.toString(8).padStart(11, '0') + '\0', 124, 12, 'ascii');
|
|
231
|
-
header.write('00000000000\0', 136, 12, 'ascii'); // mtime
|
|
232
|
-
header.write(' ', 148, 8, 'ascii'); // checksum placeholder
|
|
233
|
-
header.write(typeflag, 156, 1, 'ascii');
|
|
234
|
-
header.write('ustar\0' + '00', 257, 8, 'ascii');
|
|
235
|
-
let checksum = 0;
|
|
236
|
-
for (const byte of header)
|
|
237
|
-
checksum += byte;
|
|
238
|
-
header.write(checksum.toString(8).padStart(6, '0') + '\0 ', 148, 8, 'ascii');
|
|
239
|
-
return header;
|
|
240
|
-
}
|
|
241
|
-
function makeTarGz(entries) {
|
|
242
|
-
const blocks = [];
|
|
243
|
-
for (const entry of entries) {
|
|
244
|
-
const isDir = entry.name.endsWith('/');
|
|
245
|
-
const content = Buffer.isBuffer(entry.content)
|
|
246
|
-
? entry.content
|
|
247
|
-
: Buffer.from(entry.content ?? '', 'utf-8');
|
|
248
|
-
blocks.push(tarHeader(entry.name, isDir ? 0 : content.length, isDir ? '5' : '0'));
|
|
249
|
-
if (!isDir && content.length > 0) {
|
|
250
|
-
const padded = Buffer.alloc(Math.ceil(content.length / 512) * 512);
|
|
251
|
-
content.copy(padded);
|
|
252
|
-
blocks.push(padded);
|
|
253
|
-
}
|
|
254
|
-
}
|
|
255
|
-
blocks.push(Buffer.alloc(1024)); // end-of-archive
|
|
256
|
-
return gzipSync(Buffer.concat(blocks));
|
|
257
|
-
}
|
|
258
|
-
const SHIPPED_MANIFEST = [
|
|
259
|
-
'// swift-tools-version:5.9',
|
|
260
|
-
'import PackageDescription',
|
|
261
|
-
'let package = Package(',
|
|
262
|
-
' name: "shipped-package",',
|
|
263
|
-
' platforms: [.macOS(.v13)],',
|
|
264
|
-
' products: [.library(name: "FixtureViews", targets: ["FixtureViews"])],',
|
|
265
|
-
' targets: [.target(name: "FixtureViews")]',
|
|
266
|
-
')',
|
|
267
|
-
].join('\n');
|
|
268
|
-
/** Entries for a well-formed shipped package under the given root dir ('' = flat). */
|
|
269
|
-
function shippedPackageEntries(root = 'pkg') {
|
|
270
|
-
const prefix = root === '' ? '' : root + '/';
|
|
271
|
-
const entries = root === '' ? [] : [{ name: prefix }];
|
|
272
|
-
entries.push({ name: prefix + 'Package.swift', content: SHIPPED_MANIFEST }, {
|
|
273
|
-
name: prefix + 'Sources/FixtureViews/HomeView.swift',
|
|
274
|
-
content: 'import SwiftUI\npublic struct HomeView: View { public init() {} public var body: some View { Text("home") } }\n',
|
|
275
|
-
});
|
|
276
|
-
return entries;
|
|
277
|
-
}
|
|
278
|
-
function stageArchive(project, archive) {
|
|
279
|
-
writeFileSync(join(project.dir, '.ci', 'inputs', 'package.tar.gz'), Buffer.isBuffer(archive) ? archive : makeTarGz(archive));
|
|
280
|
-
}
|
|
281
|
-
async function buildInputsScript(extra = {}) {
|
|
282
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
283
|
-
const step = await executor.execute({ package_source: 'inputs', ...extra }, {}, testConfig);
|
|
284
|
-
return step.script;
|
|
285
|
-
}
|
|
286
|
-
function readResult(project) {
|
|
287
|
-
const resultPath = join(project.dir, '.ci', 'artifacts', 'protocol-result.json');
|
|
288
|
-
expect(existsSync(resultPath)).toBe(true);
|
|
289
|
-
return JSON.parse(readFileSync(resultPath, 'utf-8'));
|
|
290
|
-
}
|
|
291
|
-
function artifact(project, relative) {
|
|
292
|
-
return join(project.dir, '.ci', 'artifacts', relative);
|
|
293
|
-
}
|
|
294
|
-
function isPng(filePath) {
|
|
295
|
-
const head = readFileSync(filePath).subarray(0, PNG_MAGIC.length);
|
|
296
|
-
return head.equals(PNG_MAGIC);
|
|
297
|
-
}
|
|
298
|
-
/**
|
|
299
|
-
* Asserts that no error message in the result document contains an absolute
|
|
300
|
-
* runner-filesystem path: not the project dir (the script's cwd), not the OS
|
|
301
|
-
* temp dir (where harness packages and extracted shipped packages live), in
|
|
302
|
-
* either symlinked (/var/folders/...) or resolved (/private/var/...) form.
|
|
303
|
-
* Covers both per-screen errors and the build-level error object.
|
|
304
|
-
*/
|
|
305
|
-
function expectNoAbsolutePathLeaks(project, result) {
|
|
306
|
-
const forbidden = new Set();
|
|
307
|
-
for (const dir of [project.dir, tmpdir()]) {
|
|
308
|
-
forbidden.add(dir);
|
|
309
|
-
try {
|
|
310
|
-
forbidden.add(realpathSync(dir));
|
|
311
|
-
}
|
|
312
|
-
catch {
|
|
313
|
-
// nonexistent — literal form only
|
|
314
|
-
}
|
|
315
|
-
}
|
|
316
|
-
const messages = result.screens.map((screen) => screen.error?.message ?? '');
|
|
317
|
-
messages.push(result.error?.message ?? '');
|
|
318
|
-
for (const message of messages) {
|
|
319
|
-
for (const dir of forbidden) {
|
|
320
|
-
expect(message).not.toContain(dir);
|
|
321
|
-
}
|
|
322
|
-
}
|
|
323
|
-
}
|
|
324
|
-
describe('registry integration', () => {
|
|
325
|
-
test('ui-fidelity-render is registered with its input schema', () => {
|
|
326
|
-
clearRegistry();
|
|
327
|
-
initializeStepRegistry();
|
|
328
|
-
expect(hasStep('ui-fidelity-render')).toBe(true);
|
|
329
|
-
const metadata = getStepMetadata('ui-fidelity-render');
|
|
330
|
-
expect(metadata?.platform).toBe('ios');
|
|
331
|
-
const inputs = metadata?.inputs ?? {};
|
|
332
|
-
expect(Object.keys(inputs).sort()).toEqual([
|
|
333
|
-
'package_path',
|
|
334
|
-
'package_source',
|
|
335
|
-
'reference_source',
|
|
336
|
-
'render_size',
|
|
337
|
-
'scale',
|
|
338
|
-
'target',
|
|
339
|
-
]);
|
|
340
|
-
expect(inputs.reference_source.required).toBe(false);
|
|
341
|
-
expect(inputs.reference_source.default).toBe('inputs');
|
|
342
|
-
// package_path/target are enforced by the executor in repo mode (the
|
|
343
|
-
// default); a package_source:inputs config is valid without either, so
|
|
344
|
-
// neither is unconditionally required at the schema level.
|
|
345
|
-
expect(inputs.package_path.required).toBe(false);
|
|
346
|
-
expect(inputs.target.required).toBe(false);
|
|
347
|
-
expect(inputs.package_source.required).toBe(false);
|
|
348
|
-
expect(inputs.package_source.default).toBe('repo');
|
|
349
|
-
expect(inputs.render_size.required).toBe(false);
|
|
350
|
-
expect(inputs.render_size.default).toBe(DEFAULT_RENDER_SIZE);
|
|
351
|
-
expect(inputs.scale.required).toBe(false);
|
|
352
|
-
expect(inputs.scale.default).toBe(DEFAULT_SCALE);
|
|
353
|
-
});
|
|
354
|
-
});
|
|
355
|
-
describe('v1 byte compatibility', () => {
|
|
356
|
-
// Snapshot of the script generated before package_source existed, captured
|
|
357
|
-
// from the executor with package_path './MyAppPackage' and target
|
|
358
|
-
// 'MyAppViews' (defaults for everything else). Omitting package_source must
|
|
359
|
-
// keep the generated script byte-identical to that snapshot, so existing
|
|
360
|
-
// protocols are provably untouched. Update the snapshot only for a
|
|
361
|
-
// deliberate, reviewed change to the v1 runtime.
|
|
362
|
-
const V1_SCRIPT_SNAPSHOT = resolve(dirname(fileURLToPath(import.meta.url)), '../../../test/fixtures/ui-fidelity-render-v1-script.txt');
|
|
363
|
-
test('omitting package_source generates a byte-identical script', async () => {
|
|
364
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
365
|
-
const step = await executor.execute({ package_path: './MyAppPackage', target: 'MyAppViews' }, {}, testConfig);
|
|
366
|
-
expect(step.script).toBe(readFileSync(V1_SCRIPT_SNAPSHOT, 'utf-8'));
|
|
367
|
-
});
|
|
368
|
-
});
|
|
369
|
-
describe('UiFidelityRenderStepExecutor', () => {
|
|
370
|
-
test('returns a node StepDef', async () => {
|
|
371
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
372
|
-
const step = await executor.execute({ package_path: './pkg', target: 'MyViews' }, {}, testConfig);
|
|
373
|
-
expect(step.kind).toBe('node');
|
|
374
|
-
expect(step.name).toBe('ui-fidelity-render');
|
|
375
|
-
expect(step.script.length).toBeGreaterThan(0);
|
|
376
|
-
});
|
|
377
|
-
test('bakes resolved defaults into the script config', async () => {
|
|
378
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
379
|
-
const step = await executor.execute({ package_path: './pkg', target: 'MyViews' }, {}, testConfig);
|
|
380
|
-
expect(step.script).toContain('"width":393');
|
|
381
|
-
expect(step.script).toContain('"height":852');
|
|
382
|
-
expect(step.script).toContain('"scale":2');
|
|
383
|
-
expect(step.script).toContain('"target":"MyViews"');
|
|
384
|
-
});
|
|
385
|
-
test('honors explicit render_size and scale', async () => {
|
|
386
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
387
|
-
const step = await executor.execute({ package_path: './pkg', target: 'MyViews', render_size: '430x932', scale: 3 }, {}, testConfig);
|
|
388
|
-
expect(step.script).toContain('"width":430');
|
|
389
|
-
expect(step.script).toContain('"height":932');
|
|
390
|
-
expect(step.script).toContain('"scale":3');
|
|
391
|
-
});
|
|
392
|
-
test('throws when package_path is missing', async () => {
|
|
393
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
394
|
-
await expect(executor.execute({ target: 'MyViews' }, {}, testConfig)).rejects.toThrow("Missing required input 'package_path'");
|
|
395
|
-
});
|
|
396
|
-
test('throws when target is missing', async () => {
|
|
397
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
398
|
-
await expect(executor.execute({ package_path: './pkg' }, {}, testConfig)).rejects.toThrow("Missing required input 'target'");
|
|
399
|
-
});
|
|
400
|
-
test('rejects a target that is not a Swift module identifier', async () => {
|
|
401
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
402
|
-
await expect(executor.execute({ package_path: './pkg', target: 'My Views' }, {}, testConfig)).rejects.toThrow(/target/i);
|
|
403
|
-
});
|
|
404
|
-
test('rejects malformed render_size', async () => {
|
|
405
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
406
|
-
await expect(executor.execute({ package_path: './pkg', target: 'MyViews', render_size: 'huge' }, {}, testConfig)).rejects.toThrow(/render_size/);
|
|
407
|
-
});
|
|
408
|
-
});
|
|
409
|
-
describe('package_source input', () => {
|
|
410
|
-
test('rejects an unknown package_source value', async () => {
|
|
411
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
412
|
-
await expect(executor.execute({ package_path: './pkg', target: 'MyViews', package_source: 'artifact' }, {}, testConfig)).rejects.toThrow(/package_source/);
|
|
413
|
-
});
|
|
414
|
-
test('omitting package_source bakes no packageSource into the script config', async () => {
|
|
415
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
416
|
-
const step = await executor.execute({ package_path: './pkg', target: 'MyViews' }, {}, testConfig);
|
|
417
|
-
expect(step.script).not.toContain('packageSource');
|
|
418
|
-
});
|
|
419
|
-
test('explicit repo keeps the v1 inputs required and records the source', async () => {
|
|
420
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
421
|
-
const step = await executor.execute({ package_path: './pkg', target: 'MyViews', package_source: 'repo' }, {}, testConfig);
|
|
422
|
-
expect(step.kind).toBe('node');
|
|
423
|
-
expect(step.script).toContain('"packageSource":"repo"');
|
|
424
|
-
expect(step.script).toContain('"packagePath":"./pkg"');
|
|
425
|
-
await expect(executor.execute({ target: 'MyViews', package_source: 'repo' }, {}, testConfig)).rejects.toThrow("Missing required input 'package_path'");
|
|
426
|
-
await expect(executor.execute({ package_path: './pkg', package_source: 'repo' }, {}, testConfig)).rejects.toThrow("Missing required input 'target'");
|
|
427
|
-
});
|
|
428
|
-
test('inputs mode requires neither package_path nor target', async () => {
|
|
429
|
-
const script = await buildInputsScript();
|
|
430
|
-
expect(script).toContain('"packageSource":"inputs"');
|
|
431
|
-
expect(script).toContain('"packagePath":null');
|
|
432
|
-
expect(script).toContain('"target":null');
|
|
433
|
-
});
|
|
434
|
-
test('inputs mode ignores package_path and honors an explicit target', async () => {
|
|
435
|
-
const script = await buildInputsScript({ package_path: './pkg', target: 'MyViews' });
|
|
436
|
-
expect(script).toContain('"packagePath":null');
|
|
437
|
-
expect(script).toContain('"target":"MyViews"');
|
|
438
|
-
});
|
|
439
|
-
test('inputs mode still rejects a malformed target', async () => {
|
|
440
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
441
|
-
await expect(executor.execute({ package_source: 'inputs', target: 'My Views' }, {}, testConfig)).rejects.toThrow(/target/i);
|
|
442
|
-
});
|
|
443
|
-
test('validation requirements branch on the package source', () => {
|
|
444
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
445
|
-
const v1Names = executor
|
|
446
|
-
.getValidationRequirements({ package_path: './pkg', target: 'MyViews' }, {}, testConfig)
|
|
447
|
-
.map((requirement) => requirement.name);
|
|
448
|
-
expect(v1Names).toEqual(['package_path', 'target', 'swift']);
|
|
449
|
-
const inputsNames = executor
|
|
450
|
-
.getValidationRequirements({ package_source: 'inputs' }, {}, testConfig)
|
|
451
|
-
.map((requirement) => requirement.name);
|
|
452
|
-
expect(inputsNames).toEqual(['swift', 'tar']);
|
|
453
|
-
});
|
|
454
|
-
});
|
|
455
|
-
// Clone-free package delivery (fidelity-linux 03): the dispatched YAML carries
|
|
456
|
-
// a signed package_url; the runtime downloads it to .ci/inputs/package.tar.gz
|
|
457
|
-
// and proceeds like inputs mode, resolving each screen's committed reference
|
|
458
|
-
// INSIDE the extracted package (repo-relative path minus the package_path
|
|
459
|
-
// prefix) — there is no checkout on a clone-free worker. Mirrors the Android
|
|
460
|
-
// step's url mode so both platform kinds accept the same dispatched inputs.
|
|
461
|
-
describe('package_source: url', () => {
|
|
462
|
-
/**
|
|
463
|
-
* Serve one tar.gz (or a 404) from a loopback HTTP server IN A CHILD
|
|
464
|
-
* PROCESS. runScript executes the generated script via spawnSync, which
|
|
465
|
-
* blocks this process's event loop — an in-process server would deadlock
|
|
466
|
-
* against the script's curl.
|
|
467
|
-
*/
|
|
468
|
-
const SERVE_PACKAGE_SRC = [
|
|
469
|
-
"var http = require('http');",
|
|
470
|
-
"var fs = require('fs');",
|
|
471
|
-
'var filePath = process.argv[1];',
|
|
472
|
-
'var server = http.createServer(function (req, res) {',
|
|
473
|
-
" if (filePath === '-') { res.statusCode = 404; res.end('gone'); return; }",
|
|
474
|
-
" res.setHeader('content-type', 'application/gzip');",
|
|
475
|
-
' res.end(fs.readFileSync(filePath));',
|
|
476
|
-
'});',
|
|
477
|
-
"server.listen(0, '127.0.0.1', function () {",
|
|
478
|
-
" console.log('PORT ' + server.address().port);",
|
|
479
|
-
'});',
|
|
480
|
-
].join('\n');
|
|
481
|
-
async function servePackage(archive) {
|
|
482
|
-
let filePath = '-';
|
|
483
|
-
if (archive !== null) {
|
|
484
|
-
const dir = mkdtempSync(join(tmpdir(), 'ui-fidelity-url-serve-'));
|
|
485
|
-
tempDirs.push(dir);
|
|
486
|
-
filePath = join(dir, 'package.tar.gz');
|
|
487
|
-
writeFileSync(filePath, archive);
|
|
488
|
-
}
|
|
489
|
-
const child = spawn('node', ['-e', SERVE_PACKAGE_SRC, filePath], {
|
|
490
|
-
stdio: ['ignore', 'pipe', 'inherit'],
|
|
491
|
-
});
|
|
492
|
-
const port = await new Promise((resolvePort, rejectPort) => {
|
|
493
|
-
let buffered = '';
|
|
494
|
-
const timer = setTimeout(() => rejectPort(new Error('package server did not start')), 10_000);
|
|
495
|
-
child.stdout.on('data', (chunk) => {
|
|
496
|
-
buffered += chunk.toString();
|
|
497
|
-
const match = /PORT (\d+)/.exec(buffered);
|
|
498
|
-
if (match) {
|
|
499
|
-
clearTimeout(timer);
|
|
500
|
-
resolvePort(Number(match[1]));
|
|
501
|
-
}
|
|
502
|
-
});
|
|
503
|
-
child.on('exit', () => rejectPort(new Error('package server exited early')));
|
|
504
|
-
});
|
|
505
|
-
return {
|
|
506
|
-
url: `http://127.0.0.1:${port}/api/fidelity/packages/blob1?exp=1&sig=ab`,
|
|
507
|
-
close: () => new Promise((resolveClose) => {
|
|
508
|
-
child.once('exit', () => resolveClose(undefined));
|
|
509
|
-
child.kill('SIGKILL');
|
|
510
|
-
}),
|
|
511
|
-
};
|
|
512
|
-
}
|
|
513
|
-
/** Archive shaped like the dashboard's assembly: package-dir contents at the
|
|
514
|
-
* archive root, committed references under references/. */
|
|
515
|
-
function packagedProjectWithReference() {
|
|
516
|
-
return makeTarGz([
|
|
517
|
-
...shippedPackageEntries(''),
|
|
518
|
-
{
|
|
519
|
-
name: 'references/home.png',
|
|
520
|
-
content: Buffer.concat([PNG_MAGIC, Buffer.from('ref:url-home')]),
|
|
521
|
-
},
|
|
522
|
-
]);
|
|
523
|
-
}
|
|
524
|
-
test('package_source url requires package_url and package_path', async () => {
|
|
525
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
526
|
-
await expect(executor.execute({ package_source: 'url', package_path: 'ios-pkg' }, {}, testConfig)).rejects.toThrow(/package_url/);
|
|
527
|
-
await expect(executor.execute({ package_source: 'url', package_url: 'https://x.test/p' }, {}, testConfig)).rejects.toThrow(/package_path/);
|
|
528
|
-
});
|
|
529
|
-
test('rejects reference_source alongside package_source url', async () => {
|
|
530
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
531
|
-
await expect(executor.execute({
|
|
532
|
-
package_source: 'url',
|
|
533
|
-
package_url: 'https://x.test/p',
|
|
534
|
-
package_path: 'ios-pkg',
|
|
535
|
-
reference_source: 'repo',
|
|
536
|
-
}, {}, testConfig)).rejects.toThrow(/reference_source/);
|
|
537
|
-
});
|
|
538
|
-
test('url mode requires swift, tar, and curl', () => {
|
|
539
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
540
|
-
const names = executor
|
|
541
|
-
.getValidationRequirements({ package_source: 'url' }, {}, testConfig)
|
|
542
|
-
.map((requirement) => requirement.name);
|
|
543
|
-
expect(names).toEqual(['swift', 'tar', 'curl']);
|
|
544
|
-
});
|
|
545
|
-
test('downloads the package, renders, and resolves references inside it', async () => {
|
|
546
|
-
const project = makeProject({ screens: { HomeView: 'ios-pkg/references/home.png' } });
|
|
547
|
-
const served = await servePackage(packagedProjectWithReference());
|
|
548
|
-
try {
|
|
549
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
550
|
-
const step = await executor.execute({ package_source: 'url', package_url: served.url, package_path: 'ios-pkg' }, {}, testConfig);
|
|
551
|
-
const run = runScript(project, step.script);
|
|
552
|
-
expect(run.status).toBe(0);
|
|
553
|
-
const doc = readResult(project);
|
|
554
|
-
expect(doc.package_source).toBe('url');
|
|
555
|
-
const screen = doc.screens.find((s) => s.screen === 'HomeView');
|
|
556
|
-
expect(screen?.status).toBe('rendered');
|
|
557
|
-
expect(screen?.reference_image_path).toBe('ui-fidelity/references/HomeView.png');
|
|
558
|
-
expect(isPng(artifact(project, 'ui-fidelity/references/HomeView.png'))).toBe(true);
|
|
559
|
-
}
|
|
560
|
-
finally {
|
|
561
|
-
await served.close();
|
|
562
|
-
}
|
|
563
|
-
});
|
|
564
|
-
test('a failed download is one structured build error, not per-screen noise', async () => {
|
|
565
|
-
const project = makeProject({ screens: { HomeView: 'ios-pkg/references/home.png' } });
|
|
566
|
-
const served = await servePackage(null); // 404
|
|
567
|
-
try {
|
|
568
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
569
|
-
const step = await executor.execute({ package_source: 'url', package_url: served.url, package_path: 'ios-pkg' }, {}, testConfig);
|
|
570
|
-
const run = runScript(project, step.script);
|
|
571
|
-
expect(run.status).not.toBe(0);
|
|
572
|
-
const doc = readResult(project);
|
|
573
|
-
expect(doc.error?.code).toBe('PACKAGE_DOWNLOAD_FAILED');
|
|
574
|
-
const screen = doc.screens.find((s) => s.screen === 'HomeView');
|
|
575
|
-
expect(screen?.error).toBeNull();
|
|
576
|
-
expectNoAbsolutePathLeaks(project, doc);
|
|
577
|
-
}
|
|
578
|
-
finally {
|
|
579
|
-
await served.close();
|
|
580
|
-
}
|
|
581
|
-
});
|
|
582
|
-
test('a reference outside the package directory fails that screen only', async () => {
|
|
583
|
-
const project = makeProject({
|
|
584
|
-
screens: {
|
|
585
|
-
HomeView: 'ios-pkg/references/home.png',
|
|
586
|
-
Stray: 'design/elsewhere/stray.png',
|
|
587
|
-
},
|
|
588
|
-
});
|
|
589
|
-
const served = await servePackage(packagedProjectWithReference());
|
|
590
|
-
try {
|
|
591
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
592
|
-
const step = await executor.execute({ package_source: 'url', package_url: served.url, package_path: 'ios-pkg' }, {}, testConfig);
|
|
593
|
-
const run = runScript(project, step.script, { FAKE_SWIFT_RUN_SILENT: 'Stray' });
|
|
594
|
-
expect(run.status).not.toBe(0);
|
|
595
|
-
const doc = readResult(project);
|
|
596
|
-
expect(doc.screens.find((s) => s.screen === 'HomeView')?.status).toBe('rendered');
|
|
597
|
-
const stray = doc.screens.find((s) => s.screen === 'Stray');
|
|
598
|
-
expect(stray?.status).toBe('render_failed');
|
|
599
|
-
expect(stray?.error?.code).toBe('REFERENCE_MISSING');
|
|
600
|
-
}
|
|
601
|
-
finally {
|
|
602
|
-
await served.close();
|
|
603
|
-
}
|
|
604
|
-
});
|
|
605
|
-
});
|
|
606
|
-
describe('render size and scale parsing', () => {
|
|
607
|
-
test('default render size is 393x852 device points', () => {
|
|
608
|
-
expect(DEFAULT_RENDER_SIZE).toBe('393x852');
|
|
609
|
-
expect(parseRenderSize(DEFAULT_RENDER_SIZE)).toEqual({ width: 393, height: 852 });
|
|
610
|
-
});
|
|
611
|
-
test('default scale is 2', () => {
|
|
612
|
-
expect(DEFAULT_SCALE).toBe(2);
|
|
613
|
-
expect(parseScale(DEFAULT_SCALE)).toBe(2);
|
|
614
|
-
});
|
|
615
|
-
test('parses explicit sizes and string scales', () => {
|
|
616
|
-
expect(parseRenderSize('430x932')).toEqual({ width: 430, height: 932 });
|
|
617
|
-
expect(parseScale('3')).toBe(3);
|
|
618
|
-
});
|
|
619
|
-
test('rejects zero or malformed values', () => {
|
|
620
|
-
expect(() => parseRenderSize('0x852')).toThrow(/render_size/);
|
|
621
|
-
expect(() => parseRenderSize('393by852')).toThrow(/render_size/);
|
|
622
|
-
expect(() => parseScale('-1')).toThrow(/scale/);
|
|
623
|
-
expect(() => parseScale('big')).toThrow(/scale/);
|
|
624
|
-
});
|
|
625
|
-
});
|
|
626
|
-
describe('harness source generation', () => {
|
|
627
|
-
const internals = getRenderScriptInternals();
|
|
628
|
-
const options = {
|
|
629
|
-
packagePath: '/abs/path/to/user-package',
|
|
630
|
-
packageRef: 'user-package',
|
|
631
|
-
target: 'FixtureViews',
|
|
632
|
-
width: 393,
|
|
633
|
-
height: 852,
|
|
634
|
-
scale: 2,
|
|
635
|
-
};
|
|
636
|
-
test('Package.swift declares one executable target and product per screen', () => {
|
|
637
|
-
const manifest = internals.generateHarnessPackageSwift(['HomeView', 'SettingsView'], options);
|
|
638
|
-
expect(manifest).toContain('.executableTarget(name: "Render_HomeView"');
|
|
639
|
-
expect(manifest).toContain('.executableTarget(name: "Render_SettingsView"');
|
|
640
|
-
expect(manifest).toContain('.executable(name: "Render_HomeView", targets: ["Render_HomeView"])');
|
|
641
|
-
expect(manifest).toContain('.executable(name: "Render_SettingsView", targets: ["Render_SettingsView"])');
|
|
642
|
-
expect(manifest).toContain('.executableTarget(name: "RenderProbe"');
|
|
643
|
-
expect(manifest).toContain('.package(path: "/abs/path/to/user-package")');
|
|
644
|
-
expect(manifest).toContain('.product(name: "FixtureViews", package: "user-package")');
|
|
645
|
-
expect(manifest).toContain('platforms: [.macOS(.v13)]');
|
|
646
|
-
});
|
|
647
|
-
test('screen source constructs the view with a parameterless init', () => {
|
|
648
|
-
const source = internals.generateScreenSwift('HomeView', options);
|
|
649
|
-
expect(source).toContain('import FixtureViews');
|
|
650
|
-
expect(source).toContain('import SwiftUI');
|
|
651
|
-
expect(source).toContain('let view = HomeView().frame(width: width, height: height)');
|
|
652
|
-
expect(source).toContain('let width: CGFloat = 393');
|
|
653
|
-
expect(source).toContain('let height: CGFloat = 852');
|
|
654
|
-
expect(source).toContain('renderer.scale = 2');
|
|
655
|
-
expect(source).toContain('ImageRenderer(content: view)');
|
|
656
|
-
expect(source).toContain('@main');
|
|
657
|
-
expect(source).toContain('@MainActor');
|
|
658
|
-
expect(source).toContain('CGImageDestinationFinalize');
|
|
659
|
-
// Regression: .utf8 must apply to the concatenated message, not the
|
|
660
|
-
// trailing variable (caught by a real swift build).
|
|
661
|
-
expect(source).toContain('Data(("could not write PNG to " + outputPath).utf8)');
|
|
662
|
-
expect(source).not.toContain('outputPath.utf8');
|
|
663
|
-
});
|
|
664
|
-
test('probe source imports the target without referencing screens', () => {
|
|
665
|
-
const source = internals.generateProbeSwift(options);
|
|
666
|
-
expect(source).toContain('import FixtureViews');
|
|
667
|
-
expect(source).not.toContain('HomeView');
|
|
668
|
-
});
|
|
669
|
-
test('swift string literals escape quotes and backslashes', () => {
|
|
670
|
-
expect(internals.swiftStringLiteral('plain')).toBe('"plain"');
|
|
671
|
-
expect(internals.swiftStringLiteral('a"b')).toBe('"a\\"b"');
|
|
672
|
-
expect(internals.swiftStringLiteral('a\\b')).toBe('"a\\\\b"');
|
|
673
|
-
});
|
|
674
|
-
});
|
|
675
|
-
describe('render script runtime', () => {
|
|
676
|
-
test('happy path renders every screen and exits 0', async () => {
|
|
677
|
-
const project = makeProject({
|
|
678
|
-
screens: { HomeView: 'home.png', SettingsView: 'settings.png' },
|
|
679
|
-
});
|
|
680
|
-
writeReference(project, 'home.png');
|
|
681
|
-
writeReference(project, 'settings.png');
|
|
682
|
-
const script = await buildScript(project);
|
|
683
|
-
const run = runScript(project, script);
|
|
684
|
-
expect(run.status).toBe(0);
|
|
685
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
686
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/SettingsView.png'))).toBe(true);
|
|
687
|
-
expect(isPng(artifact(project, 'ui-fidelity/references/HomeView.png'))).toBe(true);
|
|
688
|
-
expect(isPng(artifact(project, 'ui-fidelity/references/SettingsView.png'))).toBe(true);
|
|
689
|
-
expect(readResult(project)).toEqual({
|
|
690
|
-
renderer: 'imagerenderer-spm',
|
|
691
|
-
screens: [
|
|
692
|
-
{
|
|
693
|
-
screen: 'HomeView',
|
|
694
|
-
status: 'rendered',
|
|
695
|
-
error: null,
|
|
696
|
-
copy_advisory: null,
|
|
697
|
-
reference_image_path: 'ui-fidelity/references/HomeView.png',
|
|
698
|
-
rendered_image_path: 'ui-fidelity/rendered/HomeView.png',
|
|
699
|
-
source_digest: expect.stringMatching(/^[0-9a-f]{64}$/),
|
|
700
|
-
},
|
|
701
|
-
{
|
|
702
|
-
screen: 'SettingsView',
|
|
703
|
-
status: 'rendered',
|
|
704
|
-
error: null,
|
|
705
|
-
copy_advisory: null,
|
|
706
|
-
reference_image_path: 'ui-fidelity/references/SettingsView.png',
|
|
707
|
-
rendered_image_path: 'ui-fidelity/rendered/SettingsView.png',
|
|
708
|
-
source_digest: expect.stringMatching(/^[0-9a-f]{64}$/),
|
|
709
|
-
},
|
|
710
|
-
],
|
|
711
|
-
});
|
|
712
|
-
});
|
|
713
|
-
test('result entries follow params.json order and use relative paths', async () => {
|
|
714
|
-
const project = makeProject({
|
|
715
|
-
screens: { ZetaView: 'zeta.png', AlphaView: 'alpha.png' },
|
|
716
|
-
});
|
|
717
|
-
writeReference(project, 'zeta.png');
|
|
718
|
-
writeReference(project, 'alpha.png');
|
|
719
|
-
const run = runScript(project, await buildScript(project));
|
|
720
|
-
expect(run.status).toBe(0);
|
|
721
|
-
const result = readResult(project);
|
|
722
|
-
expect(result.screens.map((s) => s.screen)).toEqual(['ZetaView', 'AlphaView']);
|
|
723
|
-
for (const screen of result.screens) {
|
|
724
|
-
expect(screen.reference_image_path).not.toMatch(/^\//);
|
|
725
|
-
expect(screen.rendered_image_path).not.toMatch(/^\//);
|
|
726
|
-
}
|
|
727
|
-
});
|
|
728
|
-
test('a broken screen fails alone; other screens still render', async () => {
|
|
729
|
-
const project = makeProject({
|
|
730
|
-
screens: { HomeView: 'home.png', MissingView: 'missing-view.png' },
|
|
731
|
-
});
|
|
732
|
-
writeReference(project, 'home.png');
|
|
733
|
-
writeReference(project, 'missing-view.png');
|
|
734
|
-
const run = runScript(project, await buildScript(project), {
|
|
735
|
-
FAKE_SWIFT_COMPILE_FAIL: 'MissingView',
|
|
736
|
-
});
|
|
737
|
-
expect(run.status).not.toBe(0);
|
|
738
|
-
const result = readResult(project);
|
|
739
|
-
expect(result.screens).toHaveLength(2);
|
|
740
|
-
const [home, missing] = result.screens;
|
|
741
|
-
expect(home.screen).toBe('HomeView');
|
|
742
|
-
expect(home.status).toBe('rendered');
|
|
743
|
-
expect(home.error).toBeNull();
|
|
744
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
745
|
-
expect(missing.screen).toBe('MissingView');
|
|
746
|
-
expect(missing.status).toBe('render_failed');
|
|
747
|
-
expect(missing.error?.code).toBe('VIEW_COMPILE_FAILED');
|
|
748
|
-
expect(missing.error?.message).toContain('cannot find MissingView in scope');
|
|
749
|
-
expect(missing.rendered_image_path).toBeNull();
|
|
750
|
-
expect(existsSync(artifact(project, 'ui-fidelity/rendered/MissingView.png'))).toBe(false);
|
|
751
|
-
});
|
|
752
|
-
test('a render-time crash marks only that screen RENDER_FAILED', async () => {
|
|
753
|
-
const project = makeProject({
|
|
754
|
-
screens: { HomeView: 'home.png', SettingsView: 'settings.png' },
|
|
755
|
-
});
|
|
756
|
-
writeReference(project, 'home.png');
|
|
757
|
-
writeReference(project, 'settings.png');
|
|
758
|
-
const run = runScript(project, await buildScript(project), {
|
|
759
|
-
FAKE_SWIFT_RUN_FAIL: 'SettingsView',
|
|
760
|
-
});
|
|
761
|
-
expect(run.status).not.toBe(0);
|
|
762
|
-
const result = readResult(project);
|
|
763
|
-
const [home, settings] = result.screens;
|
|
764
|
-
expect(home.status).toBe('rendered');
|
|
765
|
-
expect(settings.status).toBe('render_failed');
|
|
766
|
-
expect(settings.error?.code).toBe('RENDER_FAILED');
|
|
767
|
-
expect(settings.error?.message).toContain('render failed for SettingsView');
|
|
768
|
-
});
|
|
769
|
-
test('two screens sharing one reference get two distinct copies', async () => {
|
|
770
|
-
const project = makeProject({
|
|
771
|
-
screens: { HomeView: 'shared.png', SettingsView: 'shared.png' },
|
|
772
|
-
});
|
|
773
|
-
writeReference(project, 'shared.png');
|
|
774
|
-
const run = runScript(project, await buildScript(project));
|
|
775
|
-
expect(run.status).toBe(0);
|
|
776
|
-
const homeRef = artifact(project, 'ui-fidelity/references/HomeView.png');
|
|
777
|
-
const settingsRef = artifact(project, 'ui-fidelity/references/SettingsView.png');
|
|
778
|
-
expect(existsSync(homeRef)).toBe(true);
|
|
779
|
-
expect(existsSync(settingsRef)).toBe(true);
|
|
780
|
-
expect(readFileSync(homeRef).equals(readFileSync(settingsRef))).toBe(true);
|
|
781
|
-
const result = readResult(project);
|
|
782
|
-
expect(result.screens[0].reference_image_path).toBe('ui-fidelity/references/HomeView.png');
|
|
783
|
-
expect(result.screens[1].reference_image_path).toBe('ui-fidelity/references/SettingsView.png');
|
|
784
|
-
});
|
|
785
|
-
test('a missing reference fails that screen with REFERENCE_MISSING', async () => {
|
|
786
|
-
const project = makeProject({
|
|
787
|
-
screens: { HomeView: 'home.png', SettingsView: 'nonexistent.png' },
|
|
788
|
-
});
|
|
789
|
-
writeReference(project, 'home.png');
|
|
790
|
-
const run = runScript(project, await buildScript(project));
|
|
791
|
-
expect(run.status).not.toBe(0);
|
|
792
|
-
const result = readResult(project);
|
|
793
|
-
const [home, settings] = result.screens;
|
|
794
|
-
expect(home.status).toBe('rendered');
|
|
795
|
-
expect(settings.status).toBe('render_failed');
|
|
796
|
-
expect(settings.error?.code).toBe('REFERENCE_MISSING');
|
|
797
|
-
expect(settings.reference_image_path).toBeNull();
|
|
798
|
-
expect(settings.rendered_image_path).toBeNull();
|
|
799
|
-
expect(existsSync(artifact(project, 'ui-fidelity/references/SettingsView.png'))).toBe(false);
|
|
800
|
-
});
|
|
801
|
-
test("a reference of '..' or '.' fails only that screen with REFERENCE_MISSING", async () => {
|
|
802
|
-
const project = makeProject({
|
|
803
|
-
screens: { HomeView: '..', AboutView: '.', SettingsView: 'settings.png' },
|
|
804
|
-
});
|
|
805
|
-
writeReference(project, 'settings.png');
|
|
806
|
-
const run = runScript(project, await buildScript(project));
|
|
807
|
-
expect(run.status).not.toBe(0);
|
|
808
|
-
const [home, about, settings] = readResult(project).screens;
|
|
809
|
-
expect(home.status).toBe('render_failed');
|
|
810
|
-
expect(home.error?.code).toBe('REFERENCE_MISSING');
|
|
811
|
-
expect(about.status).toBe('render_failed');
|
|
812
|
-
expect(about.error?.code).toBe('REFERENCE_MISSING');
|
|
813
|
-
// Per-screen isolation: the valid screen still renders.
|
|
814
|
-
expect(settings.status).toBe('rendered');
|
|
815
|
-
expect(settings.error).toBeNull();
|
|
816
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/SettingsView.png'))).toBe(true);
|
|
817
|
-
});
|
|
818
|
-
test('emits a source_digest content key on a rendered screen entry', async () => {
|
|
819
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
820
|
-
writeReference(project, 'home.png');
|
|
821
|
-
// A committed package file so the digest is over real package source.
|
|
822
|
-
writeFileSync(join(project.packageDir, 'Package.swift'), '// swift-tools-version:5.9\nimport PackageDescription\n');
|
|
823
|
-
const run = runScript(project, await buildScript(project));
|
|
824
|
-
expect(run.status).toBe(0);
|
|
825
|
-
const home = readResult(project).screens[0];
|
|
826
|
-
expect(home.status).toBe('rendered');
|
|
827
|
-
expect(home.source_digest).toMatch(/^[0-9a-f]{64}$/);
|
|
828
|
-
});
|
|
829
|
-
test('a reference that is a directory fails only that screen', async () => {
|
|
830
|
-
const project = makeProject({
|
|
831
|
-
screens: { HomeView: 'home-dir.png', SettingsView: 'settings.png' },
|
|
832
|
-
});
|
|
833
|
-
mkdirSync(join(project.dir, '.ci', 'inputs', 'home-dir.png'));
|
|
834
|
-
writeReference(project, 'settings.png');
|
|
835
|
-
const run = runScript(project, await buildScript(project));
|
|
836
|
-
expect(run.status).not.toBe(0);
|
|
837
|
-
const [home, settings] = readResult(project).screens;
|
|
838
|
-
expect(home.status).toBe('render_failed');
|
|
839
|
-
expect(home.error?.code).toBe('REFERENCE_MISSING');
|
|
840
|
-
expect(home.reference_image_path).toBeNull();
|
|
841
|
-
expect(settings.status).toBe('rendered');
|
|
842
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/SettingsView.png'))).toBe(true);
|
|
843
|
-
});
|
|
844
|
-
test('a harness-level failure marks every screen RENDER_UNSUPPORTED', async () => {
|
|
845
|
-
const project = makeProject({
|
|
846
|
-
screens: { HomeView: 'home.png', SettingsView: 'settings.png' },
|
|
847
|
-
});
|
|
848
|
-
writeReference(project, 'home.png');
|
|
849
|
-
writeReference(project, 'settings.png');
|
|
850
|
-
const run = runScript(project, await buildScript(project), {
|
|
851
|
-
FAKE_SWIFT_PROBE_FAIL: '1',
|
|
852
|
-
});
|
|
853
|
-
expect(run.status).not.toBe(0);
|
|
854
|
-
const result = readResult(project);
|
|
855
|
-
expect(result.screens).toHaveLength(2);
|
|
856
|
-
for (const screen of result.screens) {
|
|
857
|
-
expect(screen.status).toBe('render_failed');
|
|
858
|
-
expect(screen.error?.code).toBe('RENDER_UNSUPPORTED');
|
|
859
|
-
expect(screen.error?.message).toContain('could not resolve package dependencies');
|
|
860
|
-
expect(screen.rendered_image_path).toBeNull();
|
|
861
|
-
}
|
|
862
|
-
});
|
|
863
|
-
test('a nonexistent package path marks every screen RENDER_UNSUPPORTED', async () => {
|
|
864
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
865
|
-
writeReference(project, 'home.png');
|
|
866
|
-
const script = await buildScript(project, {
|
|
867
|
-
package_path: join(project.dir, 'no-such-package'),
|
|
868
|
-
});
|
|
869
|
-
const run = runScript(project, script);
|
|
870
|
-
expect(run.status).not.toBe(0);
|
|
871
|
-
const result = readResult(project);
|
|
872
|
-
expect(result.screens[0].error?.code).toBe('RENDER_UNSUPPORTED');
|
|
873
|
-
});
|
|
874
|
-
test('absent params.json fails with an empty-screens result document', async () => {
|
|
875
|
-
const project = makeProject();
|
|
876
|
-
const run = runScript(project, await buildScript(project));
|
|
877
|
-
expect(run.status).not.toBe(0);
|
|
878
|
-
expect(run.stderr).toContain('params.json');
|
|
879
|
-
expect(readResult(project)).toEqual({ renderer: 'imagerenderer-spm', screens: [] });
|
|
880
|
-
});
|
|
881
|
-
test('malformed params.json fails with an empty-screens result document', async () => {
|
|
882
|
-
const project = makeProject('{ not json');
|
|
883
|
-
const run = runScript(project, await buildScript(project));
|
|
884
|
-
expect(run.status).not.toBe(0);
|
|
885
|
-
expect(run.stderr).toContain('params.json');
|
|
886
|
-
expect(readResult(project)).toEqual({ renderer: 'imagerenderer-spm', screens: [] });
|
|
887
|
-
});
|
|
888
|
-
test('params.json without a screens object fails the same way', async () => {
|
|
889
|
-
const project = makeProject({ views: { HomeView: 'home.png' } });
|
|
890
|
-
const run = runScript(project, await buildScript(project));
|
|
891
|
-
expect(run.status).not.toBe(0);
|
|
892
|
-
expect(readResult(project)).toEqual({ renderer: 'imagerenderer-spm', screens: [] });
|
|
893
|
-
});
|
|
894
|
-
test('screens given as an array fails like malformed params', async () => {
|
|
895
|
-
const project = makeProject({ screens: ['HomeView'] });
|
|
896
|
-
const run = runScript(project, await buildScript(project));
|
|
897
|
-
expect(run.status).not.toBe(0);
|
|
898
|
-
expect(readResult(project)).toEqual({ renderer: 'imagerenderer-spm', screens: [] });
|
|
899
|
-
});
|
|
900
|
-
test('an empty screens object succeeds with an empty result document', async () => {
|
|
901
|
-
const project = makeProject({ screens: {} });
|
|
902
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
903
|
-
const run = runScript(project, await buildScript(project), { FAKE_SWIFT_LOG: logPath });
|
|
904
|
-
expect(run.status).toBe(0);
|
|
905
|
-
expect(readResult(project)).toEqual({ renderer: 'imagerenderer-spm', screens: [] });
|
|
906
|
-
// No screens means the harness is never built: swift is never invoked.
|
|
907
|
-
expect(existsSync(logPath)).toBe(false);
|
|
908
|
-
});
|
|
909
|
-
test('a screen name that is not a Swift identifier never reaches generated Swift', async () => {
|
|
910
|
-
const injection = 'HomeView()); }; import Foundation //';
|
|
911
|
-
const project = makeProject({
|
|
912
|
-
screens: { [injection]: 'evil.png', SettingsView: 'settings.png' },
|
|
913
|
-
});
|
|
914
|
-
writeReference(project, 'evil.png');
|
|
915
|
-
writeReference(project, 'settings.png');
|
|
916
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
917
|
-
const run = runScript(project, await buildScript(project), {
|
|
918
|
-
FAKE_SWIFT_LOG: logPath,
|
|
919
|
-
UI_FIDELITY_KEEP_HARNESS: '1',
|
|
920
|
-
});
|
|
921
|
-
expect(run.status).not.toBe(0);
|
|
922
|
-
const [bad, good] = readResult(project).screens;
|
|
923
|
-
expect(bad.screen).toBe(injection);
|
|
924
|
-
expect(bad.status).toBe('render_failed');
|
|
925
|
-
expect(bad.error?.code).toBe('INVALID_SCREEN_NAME');
|
|
926
|
-
expect(bad.rendered_image_path).toBeNull();
|
|
927
|
-
expect(good.status).toBe('rendered');
|
|
928
|
-
// The injected name never appears in any swift invocation, and the
|
|
929
|
-
// synthesized harness contains sources only for the valid screen.
|
|
930
|
-
const invocations = readFileSync(logPath, 'utf-8');
|
|
931
|
-
expect(invocations).not.toContain('import Foundation //');
|
|
932
|
-
const probeArgs = invocations.trim().split('\n')[0].split(' ');
|
|
933
|
-
const harnessDir = probeArgs[probeArgs.indexOf('--package-path') + 1];
|
|
934
|
-
tempDirs.push(harnessDir);
|
|
935
|
-
expect(readdirSync(join(harnessDir, 'Sources')).sort()).toEqual([
|
|
936
|
-
'RenderProbe',
|
|
937
|
-
'Render_SettingsView',
|
|
938
|
-
]);
|
|
939
|
-
expect(readFileSync(join(harnessDir, 'Package.swift'), 'utf-8')).not.toContain('import Foundation //');
|
|
940
|
-
});
|
|
941
|
-
test('respects CIBUILD_ARTIFACTS_DIR', async () => {
|
|
942
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
943
|
-
writeReference(project, 'home.png');
|
|
944
|
-
const run = runScript(project, await buildScript(project), {
|
|
945
|
-
CIBUILD_ARTIFACTS_DIR: 'custom-artifacts',
|
|
946
|
-
});
|
|
947
|
-
expect(run.status).toBe(0);
|
|
948
|
-
const resultPath = join(project.dir, 'custom-artifacts', 'protocol-result.json');
|
|
949
|
-
expect(existsSync(resultPath)).toBe(true);
|
|
950
|
-
expect(isPng(join(project.dir, 'custom-artifacts', 'ui-fidelity', 'rendered', 'HomeView.png'))).toBe(true);
|
|
951
|
-
});
|
|
952
|
-
test('synthesizes the harness on disk: probe first, then per-screen build and run', async () => {
|
|
953
|
-
const project = makeProject({
|
|
954
|
-
screens: { HomeView: 'home.png', SettingsView: 'settings.png' },
|
|
955
|
-
});
|
|
956
|
-
writeReference(project, 'home.png');
|
|
957
|
-
writeReference(project, 'settings.png');
|
|
958
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
959
|
-
const run = runScript(project, await buildScript(project), {
|
|
960
|
-
FAKE_SWIFT_LOG: logPath,
|
|
961
|
-
UI_FIDELITY_KEEP_HARNESS: '1',
|
|
962
|
-
});
|
|
963
|
-
expect(run.status).toBe(0);
|
|
964
|
-
const invocations = readFileSync(logPath, 'utf-8').trim().split('\n');
|
|
965
|
-
expect(invocations[0]).toContain('--target RenderProbe');
|
|
966
|
-
expect(invocations[1]).toContain('--product Render_HomeView');
|
|
967
|
-
expect(invocations[2]).toMatch(/^run /);
|
|
968
|
-
expect(invocations[2]).toContain('Render_HomeView');
|
|
969
|
-
expect(invocations[3]).toContain('--product Render_SettingsView');
|
|
970
|
-
expect(invocations[4]).toContain('Render_SettingsView');
|
|
971
|
-
const probeArgs = invocations[0].split(' ');
|
|
972
|
-
const harnessDir = probeArgs[probeArgs.indexOf('--package-path') + 1];
|
|
973
|
-
tempDirs.push(harnessDir);
|
|
974
|
-
expect(existsSync(join(harnessDir, 'Package.swift'))).toBe(true);
|
|
975
|
-
const manifest = readFileSync(join(harnessDir, 'Package.swift'), 'utf-8');
|
|
976
|
-
expect(manifest).toContain('.executableTarget(name: "Render_HomeView"');
|
|
977
|
-
expect(manifest).toContain('.executableTarget(name: "Render_SettingsView"');
|
|
978
|
-
const homeSource = readFileSync(join(harnessDir, 'Sources', 'Render_HomeView', 'Render.swift'), 'utf-8');
|
|
979
|
-
expect(homeSource).toContain('HomeView().frame(width: width, height: height)');
|
|
980
|
-
expect(existsSync(join(harnessDir, 'Sources', 'RenderProbe', 'Probe.swift'))).toBe(true);
|
|
981
|
-
});
|
|
982
|
-
test('a nonexistent relative package path is reported as configured, never resolved', async () => {
|
|
983
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
984
|
-
writeReference(project, 'home.png');
|
|
985
|
-
const script = await buildScript(project, { package_path: 'no-such-package' });
|
|
986
|
-
const run = runScript(project, script);
|
|
987
|
-
expect(run.status).not.toBe(0);
|
|
988
|
-
const result = readResult(project);
|
|
989
|
-
expect(result.screens[0].error?.code).toBe('RENDER_UNSUPPORTED');
|
|
990
|
-
expect(result.screens[0].error?.message).toContain('no-such-package');
|
|
991
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
992
|
-
});
|
|
993
|
-
test('an absolute package path is replaced by a placeholder in error messages', async () => {
|
|
994
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
995
|
-
writeReference(project, 'home.png');
|
|
996
|
-
const script = await buildScript(project, {
|
|
997
|
-
package_path: join(project.dir, 'no-such-package'),
|
|
998
|
-
});
|
|
999
|
-
const run = runScript(project, script);
|
|
1000
|
-
expect(run.status).not.toBe(0);
|
|
1001
|
-
const result = readResult(project);
|
|
1002
|
-
expect(result.screens[0].error?.code).toBe('RENDER_UNSUPPORTED');
|
|
1003
|
-
expect(result.screens[0].error?.message).toContain('<package_path>');
|
|
1004
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1005
|
-
});
|
|
1006
|
-
test('probe-failure diagnostics are scrubbed of absolute harness and package paths', async () => {
|
|
1007
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1008
|
-
writeReference(project, 'home.png');
|
|
1009
|
-
const script = await buildScript(project, { package_path: 'user-package' });
|
|
1010
|
-
const run = runScript(project, script, { FAKE_SWIFT_PROBE_FAIL: '1' });
|
|
1011
|
-
expect(run.status).not.toBe(0);
|
|
1012
|
-
const result = readResult(project);
|
|
1013
|
-
const message = result.screens[0].error?.message ?? '';
|
|
1014
|
-
expect(result.screens[0].error?.code).toBe('RENDER_UNSUPPORTED');
|
|
1015
|
-
expect(message).toContain('the package at user-package does not build');
|
|
1016
|
-
expect(message).toContain('<harness>/Sources/RenderProbe/Probe.swift');
|
|
1017
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1018
|
-
});
|
|
1019
|
-
test('compiler diagnostics for a broken screen are scrubbed of absolute paths', async () => {
|
|
1020
|
-
const project = makeProject({
|
|
1021
|
-
screens: { HomeView: 'home.png', MissingView: 'missing-view.png' },
|
|
1022
|
-
});
|
|
1023
|
-
writeReference(project, 'home.png');
|
|
1024
|
-
writeReference(project, 'missing-view.png');
|
|
1025
|
-
const run = runScript(project, await buildScript(project), {
|
|
1026
|
-
FAKE_SWIFT_COMPILE_FAIL: 'MissingView',
|
|
1027
|
-
});
|
|
1028
|
-
expect(run.status).not.toBe(0);
|
|
1029
|
-
const result = readResult(project);
|
|
1030
|
-
const missing = result.screens[1];
|
|
1031
|
-
expect(missing.error?.code).toBe('VIEW_COMPILE_FAILED');
|
|
1032
|
-
expect(missing.error?.message).toContain('<harness>/Sources/Render_MissingView/Render.swift');
|
|
1033
|
-
expect(missing.error?.message).toContain('cannot find MissingView in scope');
|
|
1034
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1035
|
-
});
|
|
1036
|
-
test('a renderer that exits 0 without a PNG reports the artifacts-relative path', async () => {
|
|
1037
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1038
|
-
writeReference(project, 'home.png');
|
|
1039
|
-
const run = runScript(project, await buildScript(project), {
|
|
1040
|
-
FAKE_SWIFT_RUN_SILENT: 'HomeView',
|
|
1041
|
-
});
|
|
1042
|
-
expect(run.status).not.toBe(0);
|
|
1043
|
-
const result = readResult(project);
|
|
1044
|
-
expect(result.screens[0].error?.code).toBe('RENDER_FAILED');
|
|
1045
|
-
expect(result.screens[0].error?.message).toContain('.ci/artifacts/ui-fidelity/rendered/HomeView.png');
|
|
1046
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1047
|
-
});
|
|
1048
|
-
test('an absolute CIBUILD_ARTIFACTS_DIR is replaced by a placeholder in error messages', async () => {
|
|
1049
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1050
|
-
writeReference(project, 'home.png');
|
|
1051
|
-
const artifactsDir = join(project.dir, 'abs-artifacts');
|
|
1052
|
-
const run = runScript(project, await buildScript(project), {
|
|
1053
|
-
CIBUILD_ARTIFACTS_DIR: artifactsDir,
|
|
1054
|
-
FAKE_SWIFT_RUN_SILENT: 'HomeView',
|
|
1055
|
-
});
|
|
1056
|
-
expect(run.status).not.toBe(0);
|
|
1057
|
-
const resultPath = join(artifactsDir, 'protocol-result.json');
|
|
1058
|
-
expect(existsSync(resultPath)).toBe(true);
|
|
1059
|
-
const result = JSON.parse(readFileSync(resultPath, 'utf-8'));
|
|
1060
|
-
expect(result.screens[0].error?.code).toBe('RENDER_FAILED');
|
|
1061
|
-
expect(result.screens[0].error?.message).toContain('<artifacts>/ui-fidelity/rendered/HomeView.png');
|
|
1062
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1063
|
-
});
|
|
1064
|
-
test('generateRenderScript output is directly executable', () => {
|
|
1065
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1066
|
-
writeReference(project, 'home.png');
|
|
1067
|
-
const script = generateRenderScript({
|
|
1068
|
-
packagePath: project.packageDir,
|
|
1069
|
-
target: 'FixtureViews',
|
|
1070
|
-
width: 393,
|
|
1071
|
-
height: 852,
|
|
1072
|
-
scale: 2,
|
|
1073
|
-
});
|
|
1074
|
-
const run = runScript(project, script);
|
|
1075
|
-
expect(run.status).toBe(0);
|
|
1076
|
-
expect(readResult(project).screens[0].status).toBe('rendered');
|
|
1077
|
-
});
|
|
1078
|
-
});
|
|
1079
|
-
describe('render script runtime (package_source: repo)', () => {
|
|
1080
|
-
test('explicit repo renders like v1 and records the package source', async () => {
|
|
1081
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1082
|
-
writeReference(project, 'home.png');
|
|
1083
|
-
const script = await buildScript(project, { package_source: 'repo' });
|
|
1084
|
-
const run = runScript(project, script);
|
|
1085
|
-
expect(run.status).toBe(0);
|
|
1086
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
1087
|
-
expect(readResult(project)).toEqual({
|
|
1088
|
-
renderer: 'imagerenderer-spm',
|
|
1089
|
-
package_source: 'repo',
|
|
1090
|
-
error: null,
|
|
1091
|
-
screens: [
|
|
1092
|
-
{
|
|
1093
|
-
screen: 'HomeView',
|
|
1094
|
-
status: 'rendered',
|
|
1095
|
-
error: null,
|
|
1096
|
-
copy_advisory: null,
|
|
1097
|
-
reference_image_path: 'ui-fidelity/references/HomeView.png',
|
|
1098
|
-
rendered_image_path: 'ui-fidelity/rendered/HomeView.png',
|
|
1099
|
-
source_digest: expect.stringMatching(/^[0-9a-f]{64}$/),
|
|
1100
|
-
aligned_image_path: 'ui-fidelity/aligned/HomeView.png',
|
|
1101
|
-
dimensions: {
|
|
1102
|
-
rendered_px: [320, 640],
|
|
1103
|
-
logical_pt: [160, 320],
|
|
1104
|
-
reference_px: [160, 320],
|
|
1105
|
-
// the fake helper's TRIMDIMS pre-trim source px (fields 5-6)
|
|
1106
|
-
source_px: [400, 800],
|
|
1107
|
-
},
|
|
1108
|
-
},
|
|
1109
|
-
],
|
|
1110
|
-
});
|
|
1111
|
-
});
|
|
1112
|
-
test('compiles asset catalogs found in the package before rendering', async () => {
|
|
1113
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1114
|
-
writeReference(project, 'home.png');
|
|
1115
|
-
// A catalog at the package root: SwiftPM ignores it, so the renderer must
|
|
1116
|
-
// compile it with actool for Image("name") to resolve at render time.
|
|
1117
|
-
const catalog = join(project.packageDir, 'Assets', 'Media.xcassets');
|
|
1118
|
-
mkdirSync(catalog, { recursive: true });
|
|
1119
|
-
writeFileSync(join(catalog, 'Contents.json'), '{}');
|
|
1120
|
-
const script = await buildScript(project, { package_source: 'repo' });
|
|
1121
|
-
const xcrunLog = join(project.dir, 'xcrun-invocations.log');
|
|
1122
|
-
const run = runScript(project, script, { FAKE_XCRUN_LOG: xcrunLog });
|
|
1123
|
-
expect(run.status).toBe(0);
|
|
1124
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
1125
|
-
const xcrunCalls = readFileSync(xcrunLog, 'utf-8');
|
|
1126
|
-
expect(xcrunCalls).toContain('actool');
|
|
1127
|
-
expect(xcrunCalls).toContain('Media.xcassets');
|
|
1128
|
-
expect(xcrunCalls).toContain('--compile');
|
|
1129
|
-
expect(xcrunCalls).toContain('--platform macosx');
|
|
1130
|
-
});
|
|
1131
|
-
test('also compiles catalogs in module bundles for Bundle.module references', async () => {
|
|
1132
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1133
|
-
writeReference(project, 'home.png');
|
|
1134
|
-
// Source catalog drives the main-bundle (Bundle.main) compile. The module
|
|
1135
|
-
// bundle — which the fake toolchain materializes to mimic `swift build`
|
|
1136
|
-
// copying a .process resource — must ALSO be compiled in place, or an
|
|
1137
|
-
// Image("name", bundle: .module) reference finds only the raw catalog.
|
|
1138
|
-
const catalog = join(project.packageDir, 'Assets', 'Media.xcassets');
|
|
1139
|
-
mkdirSync(catalog, { recursive: true });
|
|
1140
|
-
writeFileSync(join(catalog, 'Contents.json'), '{}');
|
|
1141
|
-
const script = await buildScript(project, { package_source: 'repo' });
|
|
1142
|
-
const xcrunLog = join(project.dir, 'xcrun-invocations.log');
|
|
1143
|
-
const run = runScript(project, script, {
|
|
1144
|
-
FAKE_XCRUN_LOG: xcrunLog,
|
|
1145
|
-
FAKE_SWIFT_MODULE_CATALOG: 'FixtureViews',
|
|
1146
|
-
});
|
|
1147
|
-
expect(run.status).toBe(0);
|
|
1148
|
-
const calls = readFileSync(xcrunLog, 'utf-8').trim().split('\n');
|
|
1149
|
-
// The module bundle's own catalog is compiled into the bundle directory.
|
|
1150
|
-
const moduleCompile = calls.find((c) => c.includes('.bundle/Media.xcassets'));
|
|
1151
|
-
expect(moduleCompile).toBeTruthy();
|
|
1152
|
-
expect(moduleCompile).toMatch(/--compile \S+\.bundle(\s|$)/);
|
|
1153
|
-
});
|
|
1154
|
-
test('does not invoke actool when the package has no asset catalogs', async () => {
|
|
1155
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1156
|
-
writeReference(project, 'home.png');
|
|
1157
|
-
const script = await buildScript(project, { package_source: 'repo' });
|
|
1158
|
-
const xcrunLog = join(project.dir, 'xcrun-invocations.log');
|
|
1159
|
-
const run = runScript(project, script, { FAKE_XCRUN_LOG: xcrunLog });
|
|
1160
|
-
expect(run.status).toBe(0);
|
|
1161
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
1162
|
-
expect(existsSync(xcrunLog)).toBe(false);
|
|
1163
|
-
});
|
|
1164
|
-
test('a failing asset-catalog compile warns but still renders (warn-and-continue)', async () => {
|
|
1165
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1166
|
-
writeReference(project, 'home.png');
|
|
1167
|
-
const catalog = join(project.packageDir, 'Media.xcassets');
|
|
1168
|
-
mkdirSync(catalog, { recursive: true });
|
|
1169
|
-
writeFileSync(join(catalog, 'Contents.json'), '{}');
|
|
1170
|
-
const script = await buildScript(project, { package_source: 'repo' });
|
|
1171
|
-
const run = runScript(project, script, { FAKE_ACTOOL_FAIL: '1' });
|
|
1172
|
-
expect(run.status).toBe(0);
|
|
1173
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
1174
|
-
expect(run.stderr).toContain('asset-catalog compile failed');
|
|
1175
|
-
});
|
|
1176
|
-
});
|
|
1177
|
-
describe('render output trimming (package_source variants)', () => {
|
|
1178
|
-
// The renderer forces the view into a fixed device-sized frame, so a small
|
|
1179
|
-
// component lands in a large canvas with transparent margins. After each
|
|
1180
|
-
// screen renders, the trim helper crops the PNG in place to its content
|
|
1181
|
-
// bounding box so the rendered artifact IS the component, tight.
|
|
1182
|
-
function readArtifact(project, relative) {
|
|
1183
|
-
return readFileSync(artifact(project, relative), 'utf-8');
|
|
1184
|
-
}
|
|
1185
|
-
test('trims the rendered PNG in place via the ci fidelity-trim helper', async () => {
|
|
1186
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1187
|
-
writeReference(project, 'home.png');
|
|
1188
|
-
const script = await buildScript(project, { package_source: 'repo' });
|
|
1189
|
-
const run = runScript(project, script);
|
|
1190
|
-
expect(run.status).toBe(0);
|
|
1191
|
-
// The rendered file now holds the trim helper's output, not the raw render
|
|
1192
|
-
// (the fake ci overwrites in place, proving the in/out in-place contract).
|
|
1193
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
1194
|
-
expect(readArtifact(project, 'ui-fidelity/rendered/HomeView.png')).toContain('trimmed:HomeView.png');
|
|
1195
|
-
});
|
|
1196
|
-
test('writes an aligned image and records rendered/logical/reference dimensions', async () => {
|
|
1197
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1198
|
-
writeReference(project, 'home.png');
|
|
1199
|
-
const run = runScript(project, await buildScript(project, { package_source: 'repo' }));
|
|
1200
|
-
expect(run.status).toBe(0);
|
|
1201
|
-
// The aligned (reference-sized) image is a separate artifact.
|
|
1202
|
-
expect(isPng(artifact(project, 'ui-fidelity/aligned/HomeView.png'))).toBe(true);
|
|
1203
|
-
expect(readArtifact(project, 'ui-fidelity/aligned/HomeView.png')).toContain('aligned:HomeView.png');
|
|
1204
|
-
// Dimensions are explicit: rendered px from the helper, logical points
|
|
1205
|
-
// derived from the scale (320/2, 640/2), reference px from the helper.
|
|
1206
|
-
const screen = readResult(project).screens[0];
|
|
1207
|
-
expect(screen.aligned_image_path).toBe('ui-fidelity/aligned/HomeView.png');
|
|
1208
|
-
expect(screen.dimensions).toEqual({
|
|
1209
|
-
rendered_px: [320, 640],
|
|
1210
|
-
logical_pt: [160, 320],
|
|
1211
|
-
reference_px: [160, 320],
|
|
1212
|
-
// the fake helper's TRIMDIMS pre-trim source px (fields 5-6)
|
|
1213
|
-
source_px: [400, 800],
|
|
1214
|
-
});
|
|
1215
|
-
});
|
|
1216
|
-
test('logs how much margin the trim removed (source -> rendered)', async () => {
|
|
1217
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1218
|
-
writeReference(project, 'home.png');
|
|
1219
|
-
const run = runScript(project, await buildScript(project, { package_source: 'repo' }));
|
|
1220
|
-
expect(run.status).toBe(0);
|
|
1221
|
-
// The pre-trim source size and the trimmed size are both surfaced, so a
|
|
1222
|
-
// no-op trim (an opaque-canvas wrapper) is visible in the build log.
|
|
1223
|
-
expect(run.stdout).toContain('trimmed HomeView: 400x800 -> 320x640');
|
|
1224
|
-
});
|
|
1225
|
-
test('a failing trim warns but still renders (warn-and-continue)', async () => {
|
|
1226
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1227
|
-
writeReference(project, 'home.png');
|
|
1228
|
-
const script = await buildScript(project, { package_source: 'repo' });
|
|
1229
|
-
const run = runScript(project, script, { FAKE_TRIM_FAIL: '1' });
|
|
1230
|
-
// A trim failure never fails the step: the untrimmed render survives, with
|
|
1231
|
-
// no aligned image and no dimensions.
|
|
1232
|
-
expect(run.status).toBe(0);
|
|
1233
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
1234
|
-
const screen = readResult(project).screens[0];
|
|
1235
|
-
expect(screen.status).toBe('rendered');
|
|
1236
|
-
expect(screen.aligned_image_path).toBeNull();
|
|
1237
|
-
expect(screen.dimensions).toBeNull();
|
|
1238
|
-
expect(run.stderr).toContain('trim failed');
|
|
1239
|
-
});
|
|
1240
|
-
test('trims renders shipped via package_source: inputs too', async () => {
|
|
1241
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1242
|
-
writeReference(project, 'home.png');
|
|
1243
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1244
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1245
|
-
expect(run.status).toBe(0);
|
|
1246
|
-
expect(readArtifact(project, 'ui-fidelity/rendered/HomeView.png')).toContain('trimmed:HomeView.png');
|
|
1247
|
-
});
|
|
1248
|
-
});
|
|
1249
|
-
describe('render script runtime (package_source: inputs)', () => {
|
|
1250
|
-
test('renders every screen from a shipped archive with a single package root', async () => {
|
|
1251
|
-
const project = makeProject({
|
|
1252
|
-
screens: { HomeView: 'home.png', SettingsView: 'settings.png' },
|
|
1253
|
-
});
|
|
1254
|
-
writeReference(project, 'home.png');
|
|
1255
|
-
writeReference(project, 'settings.png');
|
|
1256
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1257
|
-
const script = await buildInputsScript({ target: 'FixtureViews' });
|
|
1258
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
1259
|
-
const run = runScript(project, script, {
|
|
1260
|
-
FAKE_SWIFT_LOG: logPath,
|
|
1261
|
-
UI_FIDELITY_KEEP_HARNESS: '1',
|
|
1262
|
-
});
|
|
1263
|
-
expect(run.status).toBe(0);
|
|
1264
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
1265
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/SettingsView.png'))).toBe(true);
|
|
1266
|
-
expect(readResult(project)).toEqual({
|
|
1267
|
-
renderer: 'imagerenderer-spm',
|
|
1268
|
-
package_source: 'inputs',
|
|
1269
|
-
error: null,
|
|
1270
|
-
screens: [
|
|
1271
|
-
{
|
|
1272
|
-
screen: 'HomeView',
|
|
1273
|
-
status: 'rendered',
|
|
1274
|
-
error: null,
|
|
1275
|
-
copy_advisory: null,
|
|
1276
|
-
reference_image_path: 'ui-fidelity/references/HomeView.png',
|
|
1277
|
-
rendered_image_path: 'ui-fidelity/rendered/HomeView.png',
|
|
1278
|
-
source_digest: expect.stringMatching(/^[0-9a-f]{64}$/),
|
|
1279
|
-
aligned_image_path: 'ui-fidelity/aligned/HomeView.png',
|
|
1280
|
-
dimensions: {
|
|
1281
|
-
rendered_px: [320, 640],
|
|
1282
|
-
logical_pt: [160, 320],
|
|
1283
|
-
reference_px: [160, 320],
|
|
1284
|
-
// the fake helper's TRIMDIMS pre-trim source px (fields 5-6)
|
|
1285
|
-
source_px: [400, 800],
|
|
1286
|
-
},
|
|
1287
|
-
},
|
|
1288
|
-
{
|
|
1289
|
-
screen: 'SettingsView',
|
|
1290
|
-
status: 'rendered',
|
|
1291
|
-
error: null,
|
|
1292
|
-
copy_advisory: null,
|
|
1293
|
-
reference_image_path: 'ui-fidelity/references/SettingsView.png',
|
|
1294
|
-
rendered_image_path: 'ui-fidelity/rendered/SettingsView.png',
|
|
1295
|
-
source_digest: expect.stringMatching(/^[0-9a-f]{64}$/),
|
|
1296
|
-
aligned_image_path: 'ui-fidelity/aligned/SettingsView.png',
|
|
1297
|
-
dimensions: {
|
|
1298
|
-
rendered_px: [320, 640],
|
|
1299
|
-
logical_pt: [160, 320],
|
|
1300
|
-
reference_px: [160, 320],
|
|
1301
|
-
// the fake helper's TRIMDIMS pre-trim source px (fields 5-6)
|
|
1302
|
-
source_px: [400, 800],
|
|
1303
|
-
},
|
|
1304
|
-
},
|
|
1305
|
-
],
|
|
1306
|
-
});
|
|
1307
|
-
// The synthesized harness depends on the EXTRACTED package, not on any
|
|
1308
|
-
// path in the repo checkout.
|
|
1309
|
-
const probeArgs = readFileSync(logPath, 'utf-8').trim().split('\n')[0].split(' ');
|
|
1310
|
-
const harnessDir = probeArgs[probeArgs.indexOf('--package-path') + 1];
|
|
1311
|
-
tempDirs.push(harnessDir);
|
|
1312
|
-
const manifest = readFileSync(join(harnessDir, 'Package.swift'), 'utf-8');
|
|
1313
|
-
expect(manifest).toMatch(/\.package\(path: "[^"]*ui-fidelity-package-[^"]*\/pkg"\)/);
|
|
1314
|
-
expect(manifest).toContain('.product(name: "FixtureViews", package: "pkg")');
|
|
1315
|
-
const extracted = manifest.match(/\.package\(path: "([^"]*)\/pkg"\)/);
|
|
1316
|
-
if (extracted)
|
|
1317
|
-
tempDirs.push(extracted[1]);
|
|
1318
|
-
});
|
|
1319
|
-
test('accepts Package.swift at the archive root', async () => {
|
|
1320
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1321
|
-
writeReference(project, 'home.png');
|
|
1322
|
-
stageArchive(project, shippedPackageEntries(''));
|
|
1323
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1324
|
-
expect(run.status).toBe(0);
|
|
1325
|
-
const result = readResult(project);
|
|
1326
|
-
expect(result.error).toBeNull();
|
|
1327
|
-
expect(result.screens[0].status).toBe('rendered');
|
|
1328
|
-
});
|
|
1329
|
-
test('ignores macOS junk entries when locating the package root', async () => {
|
|
1330
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1331
|
-
writeReference(project, 'home.png');
|
|
1332
|
-
stageArchive(project, [
|
|
1333
|
-
{ name: '.DS_Store', content: 'junk' },
|
|
1334
|
-
{ name: '._pkg', content: 'apple-double junk' },
|
|
1335
|
-
...shippedPackageEntries('pkg'),
|
|
1336
|
-
]);
|
|
1337
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1338
|
-
expect(run.status).toBe(0);
|
|
1339
|
-
expect(readResult(project).screens[0].status).toBe('rendered');
|
|
1340
|
-
});
|
|
1341
|
-
test('discovers the target from a single library product', async () => {
|
|
1342
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1343
|
-
writeReference(project, 'home.png');
|
|
1344
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1345
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
1346
|
-
const run = runScript(project, await buildInputsScript(), {
|
|
1347
|
-
FAKE_SWIFT_LOG: logPath,
|
|
1348
|
-
UI_FIDELITY_KEEP_HARNESS: '1',
|
|
1349
|
-
});
|
|
1350
|
-
expect(run.status).toBe(0);
|
|
1351
|
-
expect(readResult(project).screens[0].status).toBe('rendered');
|
|
1352
|
-
const invocations = readFileSync(logPath, 'utf-8').trim().split('\n');
|
|
1353
|
-
expect(invocations[0]).toContain('dump-package');
|
|
1354
|
-
const dumpArgs = invocations[0].split(' ');
|
|
1355
|
-
tempDirs.push(dumpArgs[dumpArgs.indexOf('--package-path') + 1]);
|
|
1356
|
-
// The discovered product is what the harness imports.
|
|
1357
|
-
const probeArgs = invocations[1].split(' ');
|
|
1358
|
-
const harnessDir = probeArgs[probeArgs.indexOf('--package-path') + 1];
|
|
1359
|
-
tempDirs.push(harnessDir);
|
|
1360
|
-
expect(readFileSync(join(harnessDir, 'Sources', 'Render_HomeView', 'Render.swift'), 'utf-8')).toContain('import FixtureViews');
|
|
1361
|
-
});
|
|
1362
|
-
test('an explicit target skips manifest discovery', async () => {
|
|
1363
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1364
|
-
writeReference(project, 'home.png');
|
|
1365
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1366
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
1367
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }), { FAKE_SWIFT_LOG: logPath });
|
|
1368
|
-
expect(run.status).toBe(0);
|
|
1369
|
-
expect(readFileSync(logPath, 'utf-8')).not.toContain('dump-package');
|
|
1370
|
-
});
|
|
1371
|
-
test('cleans up the extraction scratch directory after rendering', async () => {
|
|
1372
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1373
|
-
writeReference(project, 'home.png');
|
|
1374
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1375
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
1376
|
-
const run = runScript(project, await buildInputsScript(), { FAKE_SWIFT_LOG: logPath });
|
|
1377
|
-
expect(run.status).toBe(0);
|
|
1378
|
-
// The dump-package invocation names the extracted package root.
|
|
1379
|
-
const dumpArgs = readFileSync(logPath, 'utf-8').trim().split('\n')[0].split(' ');
|
|
1380
|
-
const extractedRoot = dumpArgs[dumpArgs.indexOf('--package-path') + 1];
|
|
1381
|
-
expect(extractedRoot).toContain('ui-fidelity-package-');
|
|
1382
|
-
expect(existsSync(extractedRoot)).toBe(false);
|
|
1383
|
-
});
|
|
1384
|
-
test('missing archive yields PACKAGE_ARCHIVE_MISSING with every screen entry present', async () => {
|
|
1385
|
-
const project = makeProject({
|
|
1386
|
-
screens: { HomeView: 'home.png', SettingsView: 'settings.png' },
|
|
1387
|
-
});
|
|
1388
|
-
writeReference(project, 'home.png');
|
|
1389
|
-
writeReference(project, 'settings.png');
|
|
1390
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
1391
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }), {
|
|
1392
|
-
FAKE_SWIFT_LOG: logPath,
|
|
1393
|
-
});
|
|
1394
|
-
expect(run.status).not.toBe(0);
|
|
1395
|
-
const result = readResult(project);
|
|
1396
|
-
expect(result.package_source).toBe('inputs');
|
|
1397
|
-
expect(result.error?.code).toBe('PACKAGE_ARCHIVE_MISSING');
|
|
1398
|
-
expect(result.error?.message).toContain('package.tar.gz');
|
|
1399
|
-
// v1 invariant: every screen entry is present even on build-level
|
|
1400
|
-
// failure, with the build error kept distinct from per-screen errors.
|
|
1401
|
-
expect(result.screens).toHaveLength(2);
|
|
1402
|
-
for (const screen of result.screens) {
|
|
1403
|
-
expect(screen.status).toBe('render_failed');
|
|
1404
|
-
expect(screen.error).toBeNull();
|
|
1405
|
-
expect(screen.reference_image_path).not.toBeNull();
|
|
1406
|
-
expect(screen.rendered_image_path).toBeNull();
|
|
1407
|
-
}
|
|
1408
|
-
// The harness never builds: swift is never invoked.
|
|
1409
|
-
expect(existsSync(logPath)).toBe(false);
|
|
1410
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1411
|
-
});
|
|
1412
|
-
test('a corrupt archive yields PACKAGE_ARCHIVE_INVALID', async () => {
|
|
1413
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1414
|
-
writeReference(project, 'home.png');
|
|
1415
|
-
stageArchive(project, Buffer.from('definitely not a gzipped tarball'));
|
|
1416
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1417
|
-
expect(run.status).not.toBe(0);
|
|
1418
|
-
const result = readResult(project);
|
|
1419
|
-
expect(result.error?.code).toBe('PACKAGE_ARCHIVE_INVALID');
|
|
1420
|
-
expect(result.screens).toHaveLength(1);
|
|
1421
|
-
expect(result.screens[0].error).toBeNull();
|
|
1422
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1423
|
-
});
|
|
1424
|
-
test('an archive with dot-dot or absolute members yields PACKAGE_ARCHIVE_INVALID', async () => {
|
|
1425
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1426
|
-
writeReference(project, 'home.png');
|
|
1427
|
-
const script = await buildInputsScript({ target: 'FixtureViews' });
|
|
1428
|
-
stageArchive(project, [
|
|
1429
|
-
...shippedPackageEntries('pkg'),
|
|
1430
|
-
{ name: 'pkg/../escape.swift', content: '// escape' },
|
|
1431
|
-
]);
|
|
1432
|
-
let run = runScript(project, script);
|
|
1433
|
-
expect(run.status).not.toBe(0);
|
|
1434
|
-
let result = readResult(project);
|
|
1435
|
-
expect(result.error?.code).toBe('PACKAGE_ARCHIVE_INVALID');
|
|
1436
|
-
expect(result.error?.message).toContain('escape.swift');
|
|
1437
|
-
stageArchive(project, [
|
|
1438
|
-
...shippedPackageEntries('pkg'),
|
|
1439
|
-
{ name: '/abs/escape.swift', content: '// escape' },
|
|
1440
|
-
]);
|
|
1441
|
-
run = runScript(project, script);
|
|
1442
|
-
expect(run.status).not.toBe(0);
|
|
1443
|
-
result = readResult(project);
|
|
1444
|
-
expect(result.error?.code).toBe('PACKAGE_ARCHIVE_INVALID');
|
|
1445
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1446
|
-
});
|
|
1447
|
-
test('an empty archive yields PACKAGE_ARCHIVE_INVALID', async () => {
|
|
1448
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1449
|
-
writeReference(project, 'home.png');
|
|
1450
|
-
stageArchive(project, []);
|
|
1451
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1452
|
-
expect(run.status).not.toBe(0);
|
|
1453
|
-
expect(readResult(project).error?.code).toBe('PACKAGE_ARCHIVE_INVALID');
|
|
1454
|
-
});
|
|
1455
|
-
test('multiple top-level roots yield PACKAGE_ARCHIVE_INVALID', async () => {
|
|
1456
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1457
|
-
writeReference(project, 'home.png');
|
|
1458
|
-
stageArchive(project, [
|
|
1459
|
-
...shippedPackageEntries('pkg-one'),
|
|
1460
|
-
...shippedPackageEntries('pkg-two'),
|
|
1461
|
-
]);
|
|
1462
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1463
|
-
expect(run.status).not.toBe(0);
|
|
1464
|
-
const result = readResult(project);
|
|
1465
|
-
expect(result.error?.code).toBe('PACKAGE_ARCHIVE_INVALID');
|
|
1466
|
-
expect(result.error?.message).toContain('pkg-one');
|
|
1467
|
-
expect(result.error?.message).toContain('pkg-two');
|
|
1468
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1469
|
-
});
|
|
1470
|
-
test('a single top-level file yields PACKAGE_ARCHIVE_INVALID', async () => {
|
|
1471
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1472
|
-
writeReference(project, 'home.png');
|
|
1473
|
-
stageArchive(project, [{ name: 'HomeView.swift', content: 'import SwiftUI\n' }]);
|
|
1474
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1475
|
-
expect(run.status).not.toBe(0);
|
|
1476
|
-
expect(readResult(project).error?.code).toBe('PACKAGE_ARCHIVE_INVALID');
|
|
1477
|
-
});
|
|
1478
|
-
test('a package root without Package.swift yields PACKAGE_MANIFEST_MISSING', async () => {
|
|
1479
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1480
|
-
writeReference(project, 'home.png');
|
|
1481
|
-
// The manifest exists, but only deeper than the single top-level root.
|
|
1482
|
-
stageArchive(project, [
|
|
1483
|
-
{ name: 'pkg/' },
|
|
1484
|
-
{ name: 'pkg/Nested/Package.swift', content: SHIPPED_MANIFEST },
|
|
1485
|
-
{ name: 'pkg/Nested/Sources/FixtureViews/HomeView.swift', content: 'import SwiftUI\n' },
|
|
1486
|
-
]);
|
|
1487
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1488
|
-
expect(run.status).not.toBe(0);
|
|
1489
|
-
const result = readResult(project);
|
|
1490
|
-
expect(result.error?.code).toBe('PACKAGE_MANIFEST_MISSING');
|
|
1491
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1492
|
-
});
|
|
1493
|
-
test('an extraction larger than the size bound yields PACKAGE_ARCHIVE_TOO_LARGE', async () => {
|
|
1494
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1495
|
-
writeReference(project, 'home.png');
|
|
1496
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1497
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }), {
|
|
1498
|
-
UI_FIDELITY_MAX_PACKAGE_BYTES: '64',
|
|
1499
|
-
});
|
|
1500
|
-
expect(run.status).not.toBe(0);
|
|
1501
|
-
const result = readResult(project);
|
|
1502
|
-
expect(result.error?.code).toBe('PACKAGE_ARCHIVE_TOO_LARGE');
|
|
1503
|
-
expect(result.error?.message).toContain('64');
|
|
1504
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1505
|
-
});
|
|
1506
|
-
test('the default extraction bound is 32 MiB', async () => {
|
|
1507
|
-
const script = await buildInputsScript({ target: 'FixtureViews' });
|
|
1508
|
-
expect(script).toContain('32 * 1024 * 1024');
|
|
1509
|
-
});
|
|
1510
|
-
test('archive validation failures fail the step even with zero screens', async () => {
|
|
1511
|
-
const project = makeProject({ screens: {} });
|
|
1512
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1513
|
-
expect(run.status).not.toBe(0);
|
|
1514
|
-
const result = readResult(project);
|
|
1515
|
-
expect(result.error?.code).toBe('PACKAGE_ARCHIVE_MISSING');
|
|
1516
|
-
expect(result.screens).toEqual([]);
|
|
1517
|
-
});
|
|
1518
|
-
test('an empty screens object with a valid archive still exits zero', async () => {
|
|
1519
|
-
const project = makeProject({ screens: {} });
|
|
1520
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1521
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1522
|
-
expect(run.status).toBe(0);
|
|
1523
|
-
expect(readResult(project)).toEqual({
|
|
1524
|
-
renderer: 'imagerenderer-spm',
|
|
1525
|
-
package_source: 'inputs',
|
|
1526
|
-
error: null,
|
|
1527
|
-
screens: [],
|
|
1528
|
-
});
|
|
1529
|
-
});
|
|
1530
|
-
test('absent params.json still records the package source with empty screens', async () => {
|
|
1531
|
-
const project = makeProject();
|
|
1532
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1533
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }));
|
|
1534
|
-
expect(run.status).not.toBe(0);
|
|
1535
|
-
expect(readResult(project)).toEqual({
|
|
1536
|
-
renderer: 'imagerenderer-spm',
|
|
1537
|
-
package_source: 'inputs',
|
|
1538
|
-
error: null,
|
|
1539
|
-
screens: [],
|
|
1540
|
-
});
|
|
1541
|
-
});
|
|
1542
|
-
test('zero library products yield PACKAGE_TARGET_UNRESOLVED', async () => {
|
|
1543
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1544
|
-
writeReference(project, 'home.png');
|
|
1545
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1546
|
-
const run = runScript(project, await buildInputsScript(), {
|
|
1547
|
-
FAKE_SWIFT_DUMP_PRODUCTS: '',
|
|
1548
|
-
});
|
|
1549
|
-
expect(run.status).not.toBe(0);
|
|
1550
|
-
const result = readResult(project);
|
|
1551
|
-
expect(result.error?.code).toBe('PACKAGE_TARGET_UNRESOLVED');
|
|
1552
|
-
expect(result.error?.message).toContain('no library products');
|
|
1553
|
-
});
|
|
1554
|
-
test('multiple library products yield PACKAGE_TARGET_UNRESOLVED naming the candidates', async () => {
|
|
1555
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1556
|
-
writeReference(project, 'home.png');
|
|
1557
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1558
|
-
const run = runScript(project, await buildInputsScript(), {
|
|
1559
|
-
FAKE_SWIFT_DUMP_PRODUCTS: 'AlphaViews,BetaViews',
|
|
1560
|
-
});
|
|
1561
|
-
expect(run.status).not.toBe(0);
|
|
1562
|
-
const result = readResult(project);
|
|
1563
|
-
expect(result.error?.code).toBe('PACKAGE_TARGET_UNRESOLVED');
|
|
1564
|
-
expect(result.error?.message).toContain('AlphaViews');
|
|
1565
|
-
expect(result.error?.message).toContain('BetaViews');
|
|
1566
|
-
});
|
|
1567
|
-
test('a failing dump-package yields PACKAGE_TARGET_UNRESOLVED', async () => {
|
|
1568
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1569
|
-
writeReference(project, 'home.png');
|
|
1570
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1571
|
-
const run = runScript(project, await buildInputsScript(), {
|
|
1572
|
-
FAKE_SWIFT_DUMP_FAIL: '1',
|
|
1573
|
-
});
|
|
1574
|
-
expect(run.status).not.toBe(0);
|
|
1575
|
-
const result = readResult(project);
|
|
1576
|
-
expect(result.error?.code).toBe('PACKAGE_TARGET_UNRESOLVED');
|
|
1577
|
-
expect(result.error?.message).toContain('malformed Package.swift manifest');
|
|
1578
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1579
|
-
});
|
|
1580
|
-
test('a discovered product that is not a Swift identifier yields PACKAGE_TARGET_UNRESOLVED', async () => {
|
|
1581
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1582
|
-
writeReference(project, 'home.png');
|
|
1583
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1584
|
-
const run = runScript(project, await buildInputsScript(), {
|
|
1585
|
-
FAKE_SWIFT_DUMP_PRODUCTS: 'My-Views',
|
|
1586
|
-
});
|
|
1587
|
-
expect(run.status).not.toBe(0);
|
|
1588
|
-
expect(readResult(project).error?.code).toBe('PACKAGE_TARGET_UNRESOLVED');
|
|
1589
|
-
});
|
|
1590
|
-
test('a broken screen fails alone in inputs mode', async () => {
|
|
1591
|
-
const project = makeProject({
|
|
1592
|
-
screens: { HomeView: 'home.png', MissingView: 'missing-view.png' },
|
|
1593
|
-
});
|
|
1594
|
-
writeReference(project, 'home.png');
|
|
1595
|
-
writeReference(project, 'missing-view.png');
|
|
1596
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1597
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }), {
|
|
1598
|
-
FAKE_SWIFT_COMPILE_FAIL: 'MissingView',
|
|
1599
|
-
});
|
|
1600
|
-
expect(run.status).not.toBe(0);
|
|
1601
|
-
const result = readResult(project);
|
|
1602
|
-
// Per-screen isolation is unchanged: the break is per-screen, the
|
|
1603
|
-
// build-level error stays null.
|
|
1604
|
-
expect(result.error).toBeNull();
|
|
1605
|
-
const [home, missing] = result.screens;
|
|
1606
|
-
expect(home.status).toBe('rendered');
|
|
1607
|
-
expect(isPng(artifact(project, 'ui-fidelity/rendered/HomeView.png'))).toBe(true);
|
|
1608
|
-
expect(missing.status).toBe('render_failed');
|
|
1609
|
-
expect(missing.error?.code).toBe('VIEW_COMPILE_FAILED');
|
|
1610
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1611
|
-
});
|
|
1612
|
-
test('a shipped package that does not build for macOS is sanitized to <package>', async () => {
|
|
1613
|
-
const project = makeProject({ screens: { HomeView: 'home.png' } });
|
|
1614
|
-
writeReference(project, 'home.png');
|
|
1615
|
-
stageArchive(project, shippedPackageEntries('pkg'));
|
|
1616
|
-
const run = runScript(project, await buildInputsScript({ target: 'FixtureViews' }), {
|
|
1617
|
-
FAKE_SWIFT_PROBE_FAIL: '1',
|
|
1618
|
-
});
|
|
1619
|
-
expect(run.status).not.toBe(0);
|
|
1620
|
-
const result = readResult(project);
|
|
1621
|
-
expect(result.error).toBeNull();
|
|
1622
|
-
expect(result.screens[0].error?.code).toBe('RENDER_UNSUPPORTED');
|
|
1623
|
-
expect(result.screens[0].error?.message).toContain('<package>/pkg');
|
|
1624
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1625
|
-
});
|
|
1626
|
-
});
|
|
1627
|
-
describe('committed fixture package', () => {
|
|
1628
|
-
test('declares the FixtureViews library with the screens the example pipeline uses', () => {
|
|
1629
|
-
const manifest = readFileSync(join(FIXTURE_PACKAGE, 'Package.swift'), 'utf-8');
|
|
1630
|
-
expect(manifest).toContain('name: "ui-fidelity-package"');
|
|
1631
|
-
expect(manifest).toContain('.library(name: "FixtureViews", targets: ["FixtureViews"])');
|
|
1632
|
-
expect(manifest).toContain('.macOS(.v13)');
|
|
1633
|
-
const sourcesDir = join(FIXTURE_PACKAGE, 'Sources', 'FixtureViews');
|
|
1634
|
-
const home = readFileSync(join(sourcesDir, 'HomeView.swift'), 'utf-8');
|
|
1635
|
-
expect(home).toContain('public struct HomeView: View');
|
|
1636
|
-
expect(home).toContain('public init()');
|
|
1637
|
-
const settings = readFileSync(join(sourcesDir, 'SettingsView.swift'), 'utf-8');
|
|
1638
|
-
expect(settings).toContain('public struct SettingsView: View');
|
|
1639
|
-
// BrokenView exists to prove per-screen isolation: its parameterless
|
|
1640
|
-
// init is deliberately unavailable, so its harness target cannot compile.
|
|
1641
|
-
const broken = readFileSync(join(sourcesDir, 'BrokenView.swift'), 'utf-8');
|
|
1642
|
-
expect(broken).toContain('public struct BrokenView: View');
|
|
1643
|
-
expect(broken).toContain('@available(*, unavailable');
|
|
1644
|
-
});
|
|
1645
|
-
test('wires through the harness as package_path (fake toolchain)', async () => {
|
|
1646
|
-
const project = makeProject({
|
|
1647
|
-
screens: { HomeView: 'home.png', SettingsView: 'settings.png' },
|
|
1648
|
-
});
|
|
1649
|
-
writeReference(project, 'home.png');
|
|
1650
|
-
writeReference(project, 'settings.png');
|
|
1651
|
-
const script = await buildScript(project, { package_path: FIXTURE_PACKAGE });
|
|
1652
|
-
const logPath = join(project.dir, 'swift-invocations.log');
|
|
1653
|
-
const run = runScript(project, script, {
|
|
1654
|
-
FAKE_SWIFT_LOG: logPath,
|
|
1655
|
-
UI_FIDELITY_KEEP_HARNESS: '1',
|
|
1656
|
-
});
|
|
1657
|
-
expect(run.status).toBe(0);
|
|
1658
|
-
const result = readResult(project);
|
|
1659
|
-
expect(result.screens.map((s) => s.status)).toEqual(['rendered', 'rendered']);
|
|
1660
|
-
// The synthesized harness depends on the real fixture package on disk.
|
|
1661
|
-
const probeArgs = readFileSync(logPath, 'utf-8').trim().split('\n')[0].split(' ');
|
|
1662
|
-
const harnessDir = probeArgs[probeArgs.indexOf('--package-path') + 1];
|
|
1663
|
-
tempDirs.push(harnessDir);
|
|
1664
|
-
const manifest = readFileSync(join(harnessDir, 'Package.swift'), 'utf-8');
|
|
1665
|
-
expect(manifest).toContain('.package(path: "' + FIXTURE_PACKAGE + '")');
|
|
1666
|
-
expect(manifest).toContain('.product(name: "FixtureViews", package: "ui-fidelity-package")');
|
|
1667
|
-
});
|
|
1668
|
-
// Opt-in: builds the fixture with the real Swift toolchain and renders
|
|
1669
|
-
// through ImageRenderer. Requires macOS with Xcode; takes a SwiftPM
|
|
1670
|
-
// build, so it is skipped unless explicitly requested.
|
|
1671
|
-
const realSwiftTest = process.env.CIBUILD_UI_FIDELITY_REAL_SWIFT === '1' ? test : test.skip;
|
|
1672
|
-
realSwiftTest('real toolchain: renders HomeView and isolates BrokenView with sanitized diagnostics', async () => {
|
|
1673
|
-
const project = makeProject({
|
|
1674
|
-
screens: { HomeView: 'home.png', BrokenView: 'broken.png' },
|
|
1675
|
-
});
|
|
1676
|
-
writeReference(project, 'home.png');
|
|
1677
|
-
writeReference(project, 'broken.png');
|
|
1678
|
-
const script = await buildScript(project, { package_path: FIXTURE_PACKAGE });
|
|
1679
|
-
// No PATH shim: the real `swift` binary does the work.
|
|
1680
|
-
const run = spawnSync('node', ['-e', script], {
|
|
1681
|
-
cwd: project.dir,
|
|
1682
|
-
encoding: 'utf-8',
|
|
1683
|
-
maxBuffer: 32 * 1024 * 1024,
|
|
1684
|
-
});
|
|
1685
|
-
expect(run.status).not.toBe(0);
|
|
1686
|
-
const result = readResult(project);
|
|
1687
|
-
const [home, broken] = result.screens;
|
|
1688
|
-
expect(home.status).toBe('rendered');
|
|
1689
|
-
expect(home.error).toBeNull();
|
|
1690
|
-
const rendered = artifact(project, 'ui-fidelity/rendered/HomeView.png');
|
|
1691
|
-
expect(isPng(rendered)).toBe(true);
|
|
1692
|
-
expect(readFileSync(rendered).length).toBeGreaterThan(1000);
|
|
1693
|
-
expect(broken.status).toBe('render_failed');
|
|
1694
|
-
expect(broken.error?.code).toBe('VIEW_COMPILE_FAILED');
|
|
1695
|
-
expect(broken.error?.message).toContain('BrokenView');
|
|
1696
|
-
expect(broken.rendered_image_path).toBeNull();
|
|
1697
|
-
// Real compiler diagnostics reference the harness temp dir; none of
|
|
1698
|
-
// those absolute paths may reach the result document.
|
|
1699
|
-
expectNoAbsolutePathLeaks(project, result);
|
|
1700
|
-
}, 600_000);
|
|
1701
|
-
});
|
|
1702
|
-
describe('reference_source: repo', () => {
|
|
1703
|
-
test('rejects an unknown reference_source value', async () => {
|
|
1704
|
-
const executor = new UiFidelityRenderStepExecutor();
|
|
1705
|
-
await expect(executor.execute({ package_path: './pkg', target: 'MyViews', reference_source: 'artifact' }, {}, testConfig)).rejects.toThrow(/reference_source/);
|
|
1706
|
-
});
|
|
1707
|
-
test('omitting reference_source bakes no referenceSource into the script config', async () => {
|
|
1708
|
-
const script = await buildScript(makeProject());
|
|
1709
|
-
const configLine = script
|
|
1710
|
-
.split('\n')
|
|
1711
|
-
.find((l) => l.startsWith('var CONFIG = '));
|
|
1712
|
-
expect(configLine).toBeDefined();
|
|
1713
|
-
expect(configLine).not.toContain('referenceSource');
|
|
1714
|
-
});
|
|
1715
|
-
test('reads each screen reference from its repo-relative path in the checkout', async () => {
|
|
1716
|
-
const project = makeProject({
|
|
1717
|
-
screens: { HomeView: 'refs/home.png', SettingsView: 'design/settings.png' },
|
|
1718
|
-
});
|
|
1719
|
-
// Committed references live at repo-relative paths (resolved against cwd),
|
|
1720
|
-
// NOT in .ci/inputs.
|
|
1721
|
-
mkdirSync(join(project.dir, 'refs'), { recursive: true });
|
|
1722
|
-
mkdirSync(join(project.dir, 'design'), { recursive: true });
|
|
1723
|
-
writeFileSync(join(project.dir, 'refs', 'home.png'), Buffer.concat([PNG_MAGIC, Buffer.from('ref:home')]));
|
|
1724
|
-
writeFileSync(join(project.dir, 'design', 'settings.png'), Buffer.concat([PNG_MAGIC, Buffer.from('ref:settings')]));
|
|
1725
|
-
const script = await buildScript(project, { reference_source: 'repo' });
|
|
1726
|
-
const run = runScript(project, script);
|
|
1727
|
-
expect(run.status).toBe(0);
|
|
1728
|
-
expect(isPng(artifact(project, 'ui-fidelity/references/HomeView.png'))).toBe(true);
|
|
1729
|
-
expect(isPng(artifact(project, 'ui-fidelity/references/SettingsView.png'))).toBe(true);
|
|
1730
|
-
const result = readResult(project);
|
|
1731
|
-
expect(result.screens.map((s) => s.status)).toEqual(['rendered', 'rendered']);
|
|
1732
|
-
expect(result.screens.map((s) => s.reference_image_path)).toEqual([
|
|
1733
|
-
'ui-fidelity/references/HomeView.png',
|
|
1734
|
-
'ui-fidelity/references/SettingsView.png',
|
|
1735
|
-
]);
|
|
1736
|
-
});
|
|
1737
|
-
test('fails one screen when its committed reference is missing, renders the rest', async () => {
|
|
1738
|
-
const project = makeProject({
|
|
1739
|
-
screens: { HomeView: 'refs/home.png', GhostView: 'refs/ghost.png' },
|
|
1740
|
-
});
|
|
1741
|
-
mkdirSync(join(project.dir, 'refs'), { recursive: true });
|
|
1742
|
-
writeFileSync(join(project.dir, 'refs', 'home.png'), Buffer.concat([PNG_MAGIC, Buffer.from('ref:home')]));
|
|
1743
|
-
// refs/ghost.png intentionally absent.
|
|
1744
|
-
const run = runScript(project, await buildScript(project, { reference_source: 'repo' }));
|
|
1745
|
-
expect(run.status).not.toBe(0);
|
|
1746
|
-
const result = readResult(project);
|
|
1747
|
-
const home = result.screens.find((s) => s.screen === 'HomeView');
|
|
1748
|
-
const ghost = result.screens.find((s) => s.screen === 'GhostView');
|
|
1749
|
-
expect(home?.status).toBe('rendered');
|
|
1750
|
-
expect(ghost?.status).toBe('render_failed');
|
|
1751
|
-
expect(ghost?.error?.code).toBe('REFERENCE_MISSING');
|
|
1752
|
-
});
|
|
1753
|
-
});
|
|
1754
|
-
describe('render script runtime — reference provenance', () => {
|
|
1755
|
-
const GOOD_PROV = {
|
|
1756
|
-
figmaNode: '12:345',
|
|
1757
|
-
locale: 'en',
|
|
1758
|
-
resolved: true,
|
|
1759
|
-
exportedAt: '2026-06-18T10:00:00Z',
|
|
1760
|
-
};
|
|
1761
|
-
test('a placeholder reference (resolved=false) renders and downgrades only the copy channel', async () => {
|
|
1762
|
-
const project = makeProject({
|
|
1763
|
-
screens: { HomeView: 'home.png' },
|
|
1764
|
-
render_locale: 'en',
|
|
1765
|
-
provenance: { HomeView: { ...GOOD_PROV, resolved: false } },
|
|
1766
|
-
});
|
|
1767
|
-
writeReference(project, 'home.png');
|
|
1768
|
-
const run = runScript(project, await buildScript(project));
|
|
1769
|
-
expect(run.status).toBe(0);
|
|
1770
|
-
const home = readResult(project).screens[0];
|
|
1771
|
-
expect(home.status).toBe('rendered');
|
|
1772
|
-
expect(home.error).toBeNull();
|
|
1773
|
-
expect(home.copy_advisory?.code).toBe('copy_unverifiable');
|
|
1774
|
-
expect(home.copy_advisory?.reason).toBe('unresolved_placeholders');
|
|
1775
|
-
});
|
|
1776
|
-
test('the placeholder downgrade is identical without a render_locale (same input, same verdict)', async () => {
|
|
1777
|
-
const project = makeProject({
|
|
1778
|
-
screens: { HomeView: 'home.png' },
|
|
1779
|
-
provenance: { HomeView: { ...GOOD_PROV, resolved: false } },
|
|
1780
|
-
});
|
|
1781
|
-
writeReference(project, 'home.png');
|
|
1782
|
-
const run = runScript(project, await buildScript(project));
|
|
1783
|
-
expect(run.status).toBe(0);
|
|
1784
|
-
const home = readResult(project).screens[0];
|
|
1785
|
-
expect(home.status).toBe('rendered');
|
|
1786
|
-
expect(home.copy_advisory?.reason).toBe('unresolved_placeholders');
|
|
1787
|
-
});
|
|
1788
|
-
test('an Italian reference against an English render downgrades the copy channel', async () => {
|
|
1789
|
-
const project = makeProject({
|
|
1790
|
-
screens: { HomeView: 'home.png' },
|
|
1791
|
-
render_locale: 'en',
|
|
1792
|
-
provenance: { HomeView: { ...GOOD_PROV, locale: 'it' } },
|
|
1793
|
-
});
|
|
1794
|
-
writeReference(project, 'home.png');
|
|
1795
|
-
const run = runScript(project, await buildScript(project));
|
|
1796
|
-
expect(run.status).toBe(0);
|
|
1797
|
-
const home = readResult(project).screens[0];
|
|
1798
|
-
expect(home.status).toBe('rendered');
|
|
1799
|
-
expect(home.copy_advisory?.reason).toBe('locale_mismatch');
|
|
1800
|
-
});
|
|
1801
|
-
test('locale comparison requires a declared render_locale — its absence skips only that', async () => {
|
|
1802
|
-
const project = makeProject({
|
|
1803
|
-
screens: { HomeView: 'home.png' },
|
|
1804
|
-
provenance: { HomeView: { ...GOOD_PROV, locale: 'it' } },
|
|
1805
|
-
});
|
|
1806
|
-
writeReference(project, 'home.png');
|
|
1807
|
-
const run = runScript(project, await buildScript(project));
|
|
1808
|
-
expect(run.status).toBe(0);
|
|
1809
|
-
const home = readResult(project).screens[0];
|
|
1810
|
-
expect(home.status).toBe('rendered');
|
|
1811
|
-
expect(home.copy_advisory ?? null).toBeNull();
|
|
1812
|
-
});
|
|
1813
|
-
test('an absent reference with provenance is fatal (REFERENCE_ABSENT, unverifiable)', async () => {
|
|
1814
|
-
const project = makeProject({
|
|
1815
|
-
screens: { HomeView: 'home.png' },
|
|
1816
|
-
provenance: { HomeView: GOOD_PROV },
|
|
1817
|
-
});
|
|
1818
|
-
// No writeReference — the file is absent.
|
|
1819
|
-
const run = runScript(project, await buildScript(project));
|
|
1820
|
-
expect(run.status).not.toBe(0);
|
|
1821
|
-
const home = readResult(project).screens[0];
|
|
1822
|
-
expect(home.status).toBe('unverifiable');
|
|
1823
|
-
expect(home.error?.code).toBe('REFERENCE_ABSENT');
|
|
1824
|
-
});
|
|
1825
|
-
test('a clean provenanced reference renders with no copy advisory', async () => {
|
|
1826
|
-
const project = makeProject({
|
|
1827
|
-
screens: { HomeView: 'home.png' },
|
|
1828
|
-
render_locale: 'en',
|
|
1829
|
-
provenance: { HomeView: GOOD_PROV },
|
|
1830
|
-
});
|
|
1831
|
-
writeReference(project, 'home.png');
|
|
1832
|
-
const run = runScript(project, await buildScript(project));
|
|
1833
|
-
expect(run.status).toBe(0);
|
|
1834
|
-
const home = readResult(project).screens[0];
|
|
1835
|
-
expect(home.status).toBe('rendered');
|
|
1836
|
-
expect(home.copy_advisory ?? null).toBeNull();
|
|
1837
|
-
});
|
|
1838
|
-
test('an unprovenanced screen keeps its legacy behaviour even with a render_locale (no NO_PROVENANCE arming on iOS)', async () => {
|
|
1839
|
-
// NO_PROVENANCE hard-arming is Android-legacy only; the iOS step never
|
|
1840
|
-
// refused unprovenanced screens and must not start now.
|
|
1841
|
-
const project = makeProject({
|
|
1842
|
-
screens: { HomeView: 'home.png' },
|
|
1843
|
-
render_locale: 'en',
|
|
1844
|
-
provenance: {},
|
|
1845
|
-
});
|
|
1846
|
-
writeReference(project, 'home.png');
|
|
1847
|
-
const run = runScript(project, await buildScript(project));
|
|
1848
|
-
expect(run.status).toBe(0);
|
|
1849
|
-
const home = readResult(project).screens[0];
|
|
1850
|
-
expect(home.status).toBe('rendered');
|
|
1851
|
-
expect(home.copy_advisory ?? null).toBeNull();
|
|
1852
|
-
});
|
|
1853
|
-
test('independent verdicts: a clean sibling carries no advisory while the placeholder state does', async () => {
|
|
1854
|
-
const project = makeProject({
|
|
1855
|
-
screens: { HomeView: 'home.png', SettingsView: 'settings.png' },
|
|
1856
|
-
provenance: {
|
|
1857
|
-
HomeView: GOOD_PROV,
|
|
1858
|
-
SettingsView: { ...GOOD_PROV, resolved: false },
|
|
1859
|
-
},
|
|
1860
|
-
});
|
|
1861
|
-
writeReference(project, 'home.png');
|
|
1862
|
-
writeReference(project, 'settings.png');
|
|
1863
|
-
const run = runScript(project, await buildScript(project));
|
|
1864
|
-
expect(run.status).toBe(0);
|
|
1865
|
-
const [home, settings] = readResult(project).screens;
|
|
1866
|
-
expect(home.copy_advisory ?? null).toBeNull();
|
|
1867
|
-
expect(settings.status).toBe('rendered');
|
|
1868
|
-
expect(settings.copy_advisory?.reason).toBe('unresolved_placeholders');
|
|
1869
|
-
});
|
|
1870
|
-
});
|
|
1871
|
-
//# sourceMappingURL=ui-fidelity-render.test.js.map
|