@synopackageland/cli 0.4.1 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -3
- package/dist/build-spk/delete-app-data.d.ts +27 -7
- package/dist/build-spk/delete-app-data.js +35 -34
- package/dist/build-spk/dsm-arch.d.ts +6 -1
- package/dist/build-spk/dsm-arch.js +25 -1
- package/dist/build-spk/dsm-version.d.ts +14 -0
- package/dist/build-spk/dsm-version.js +47 -0
- package/dist/build-spk/gateway.d.ts +31 -0
- package/dist/build-spk/gateway.js +55 -0
- package/dist/build-spk/generator.d.ts +5 -0
- package/dist/build-spk/generator.js +103 -107
- package/dist/build-spk/install-wizard.d.ts +20 -0
- package/dist/build-spk/install-wizard.js +76 -0
- package/dist/build-spk/native-lifecycle.d.ts +5 -0
- package/dist/build-spk/native-lifecycle.js +12 -2
- package/dist/build-spk/resource-worker.d.ts +9 -4
- package/dist/build-spk/resource-worker.js +15 -6
- package/dist/build-spk/share-combobox.d.ts +3 -0
- package/dist/build-spk/share-combobox.js +27 -0
- package/dist/build-spk/uninstall-wizard.d.ts +14 -0
- package/dist/build-spk/uninstall-wizard.js +33 -0
- package/dist/build-spk/wizard-i18n.d.ts +18 -0
- package/dist/build-spk/wizard-i18n.js +38 -0
- package/dist/build-spk/wizard-messages.d.ts +388 -0
- package/dist/build-spk/wizard-messages.js +387 -0
- package/dist/cli.js +130 -30
- package/dist/compose/analyze.d.ts +20 -0
- package/dist/compose/analyze.js +75 -11
- package/dist/contract/validate.js +33 -1
- package/dist/contract/web-launch.d.ts +22 -0
- package/dist/contract/web-launch.js +37 -0
- package/dist/deploy/deploy.d.ts +3 -0
- package/dist/deploy/deploy.js +11 -2
- package/dist/error-registry.js +60 -1
- package/dist/host/open-url.d.ts +2 -0
- package/dist/host/open-url.js +4 -1
- package/dist/images/pin.d.ts +52 -0
- package/dist/images/pin.js +249 -0
- package/dist/init.d.ts +27 -3
- package/dist/init.js +104 -15
- package/dist/inspect.d.ts +3 -0
- package/dist/inspect.js +15 -3
- package/dist/standalone/background.d.ts +21 -0
- package/dist/standalone/background.js +52 -0
- package/dist/standalone/binary.d.ts +1 -0
- package/dist/standalone/binary.js +1 -1
- package/dist/standalone/build.d.ts +2 -0
- package/dist/standalone/build.js +38 -6
- package/dist/standalone/dsm-window.js +2 -2
- package/dist/standalone/helper-inventory.d.ts +15 -0
- package/dist/standalone/helper-inventory.js +9 -0
- package/dist/standalone/helper.d.ts +1 -0
- package/dist/standalone/helper.js +28 -10
- package/dist/standalone/host-adapter.d.ts +1 -1
- package/dist/standalone/host-adapter.js +33 -15
- package/dist/standalone/host-file-navigation.d.ts +7 -0
- package/dist/standalone/host-file-navigation.js +172 -0
- package/dist/standalone/inspect-helper.js +9 -4
- package/dist/standalone/inspect-spk.d.ts +1 -1
- package/dist/standalone/inspect-spk.js +200 -7
- package/dist/standalone/job-diagnostics.d.ts +1 -0
- package/dist/standalone/job-diagnostics.js +49 -0
- package/dist/standalone/lifecycle.js +12 -2
- package/dist/standalone/manifest.d.ts +15 -2
- package/dist/standalone/manifest.js +37 -6
- package/dist/standalone/native-layout.d.ts +74 -0
- package/dist/standalone/native-layout.js +318 -0
- package/dist/standalone/resource.d.ts +11 -0
- package/dist/standalone/resource.js +13 -0
- package/dist/standalone/root-window.d.ts +3 -1
- package/dist/standalone/root-window.js +156 -38
- package/dist/standalone/shared-folder.d.ts +39 -0
- package/dist/standalone/shared-folder.js +141 -0
- package/dist/standalone/spk.d.ts +2 -0
- package/dist/standalone/spk.js +33 -3
- package/dist/standalone/worker-contract-json.d.ts +8 -0
- package/dist/standalone/worker-contract-json.js +141 -0
- package/dist/standalone/worker-contract.d.ts +189 -0
- package/dist/standalone/worker-contract.js +175 -0
- package/dist/standalone/worker-registry.d.ts +40 -0
- package/dist/standalone/worker-registry.js +167 -0
- package/dist/templates/_daemon/go.mod +2 -0
- package/dist/templates/_gateway/auth.go +101 -0
- package/dist/templates/_gateway/auth_test.go +216 -0
- package/dist/templates/_gateway/cgi.go +133 -0
- package/dist/templates/_gateway/cgi_test.go +156 -0
- package/dist/templates/_gateway/csrf.go +73 -0
- package/dist/templates/_gateway/csrf_test.go +155 -0
- package/dist/templates/_gateway/directory.go +108 -0
- package/dist/templates/_gateway/directory_test.go +202 -0
- package/dist/templates/_gateway/gateway.go +412 -0
- package/dist/templates/_gateway/gateway_test.go +676 -0
- package/dist/templates/_gateway/go.mod +3 -0
- package/dist/templates/_gateway/groups.go +66 -0
- package/dist/templates/_gateway/main.go +45 -0
- package/dist/templates/binary/synology-app.yaml +1 -1
- package/dist/templates/compose-import/compose.yaml +1 -1
- package/dist/templates/compose-import/synology-app.yaml +1 -1
- package/dist/templates/hello-files/compose.yaml +1 -1
- package/dist/templates/hello-files/synology-app.yaml +1 -1
- package/dist/templates/hello-web/compose.yaml +5 -2
- package/dist/templates/hello-web/synology-app.yaml +4 -1
- package/dist/templates/hello-web-page/compose.yaml +5 -2
- package/dist/templates/hello-web-page/synology-app.yaml +4 -1
- package/dist/templates/native-go/README.md +93 -4
- package/dist/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +3 -1
- package/dist/templates/native-go/backend/cmd/app/main.go +17 -0
- package/dist/templates/native-go/backend/cmd/app/sdk_main.go +60 -0
- package/dist/templates/native-go/backend/cmd/background-worker/main.go +38 -0
- package/dist/templates/native-go/backend/cmd/helper/main.go +10 -1
- package/dist/templates/native-go/backend/cmd/helper/sdk_registry.go +19 -0
- package/dist/templates/native-go/backend/go.mod +5 -3
- package/dist/templates/native-go/backend/go.sum +4 -4
- package/dist/templates/native-go/backend/internal/api/registry.go +17 -6
- package/dist/templates/native-go/backend/internal/api/server.go +7 -1
- package/dist/templates/native-go/backend/internal/app/background.go +270 -0
- package/dist/templates/native-go/backend/internal/app/background_none.go +8 -0
- package/dist/templates/native-go/backend/internal/app/background_owner.go +59 -0
- package/dist/templates/native-go/backend/internal/app/background_owner_test.go +145 -0
- package/dist/templates/native-go/backend/internal/app/background_schedule.go +74 -0
- package/dist/templates/native-go/backend/internal/app/background_schedule_test.go +62 -0
- package/dist/templates/native-go/backend/internal/app/background_test.go +346 -0
- package/dist/templates/native-go/backend/internal/app/integration_state.go +16 -0
- package/dist/templates/native-go/backend/internal/app/migrations.go +41 -0
- package/dist/templates/native-go/backend/internal/app/migrations_test.go +41 -0
- package/dist/templates/native-go/backend/internal/app/nas_cgi_artifact_test.go +8 -0
- package/dist/templates/native-go/backend/internal/app/routes.go +17 -3
- package/dist/templates/native-go/backend/internal/app/routes_test.go +20 -3
- package/dist/templates/native-go/backend/internal/app/sdk_jobs.go +70 -0
- package/dist/templates/native-go/backend/internal/app/sdk_jobs_test.go +116 -0
- package/dist/templates/native-go/backend/internal/app/sdk_routes.go +174 -0
- package/dist/templates/native-go/backend/internal/app/session_files.go +15 -0
- package/dist/templates/native-go/backend/internal/app/session_files_test.go +26 -0
- package/dist/templates/native-go/backend/internal/app/shared_folder.go +144 -0
- package/dist/templates/native-go/backend/internal/app/shared_folder_test.go +158 -0
- package/dist/templates/native-go/backend/internal/background/access.go +57 -0
- package/dist/templates/native-go/backend/internal/background/owner_test.go +126 -0
- package/dist/templates/native-go/backend/internal/background/runner.go +185 -0
- package/dist/templates/native-go/backend/internal/background/runner_test.go +209 -0
- package/dist/templates/native-go/backend/internal/background/schedule.go +300 -0
- package/dist/templates/native-go/backend/internal/background/schedule_test.go +335 -0
- package/dist/templates/native-go/backend/internal/background/store.go +807 -0
- package/dist/templates/native-go/backend/internal/background/store_test.go +407 -0
- package/dist/templates/native-go/backend/internal/datamigrate/migrate.go +191 -0
- package/dist/templates/native-go/backend/internal/datamigrate/migrate_test.go +177 -0
- package/dist/templates/native-go/backend/internal/filestation/browse_test.go +67 -0
- package/dist/templates/native-go/backend/internal/filestation/client.go +251 -0
- package/dist/templates/native-go/backend/internal/filestation/client_test.go +155 -0
- package/dist/templates/native-go/backend/internal/filestation/doc.go +4 -0
- package/dist/templates/native-go/backend/internal/filestation/read_file_test.go +90 -0
- package/dist/templates/native-go/backend/internal/filestation/repository.go +207 -0
- package/dist/templates/native-go/backend/internal/filestation/repository_test.go +104 -0
- package/dist/templates/native-go/backend/internal/filestation/sdk_session.go +241 -0
- package/dist/templates/native-go/backend/internal/filestation/sdk_session_test.go +183 -0
- package/dist/templates/native-go/backend/internal/filestation/service.go +254 -0
- package/dist/templates/native-go/backend/internal/filestation/service_test.go +94 -0
- package/dist/templates/native-go/backend/internal/filestation/session_semantics_test.go +383 -0
- package/dist/templates/native-go/backend/internal/filestation/write.go +72 -0
- package/dist/templates/native-go/backend/internal/filestation/write_test.go +123 -0
- package/dist/templates/native-go/backend/internal/helper/background_proof.go +163 -0
- package/dist/templates/native-go/backend/internal/helper/background_proof_contract.go +46 -0
- package/dist/templates/native-go/backend/internal/helper/background_proof_test.go +161 -0
- package/dist/templates/native-go/backend/internal/helper/data_contract.go +3 -0
- package/dist/templates/native-go/backend/internal/helper/data_protocol.go +3 -0
- package/dist/templates/native-go/backend/internal/helper/data_protocol_test.go +1 -1
- package/dist/templates/native-go/backend/internal/helper/grant_authority.go +3 -0
- package/dist/templates/native-go/backend/internal/helper/grant_authority_test.go +1 -1
- package/dist/templates/native-go/backend/internal/helper/grant_repository_linux.go +2 -1
- package/dist/templates/native-go/backend/internal/helper/grant_repository_linux_test.go +4 -3
- package/dist/templates/native-go/backend/internal/helper/grant_snapshot.go +3 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/bindings.go +48 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/contract_test.go +436 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/decode.go +185 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/digest.go +55 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/registry.go +85 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/registry_test.go +73 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/result.go +53 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/stub.go +8 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/testdata/input-digests.json +127 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/types.go +234 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/types_test.go +29 -0
- package/dist/templates/native-go/backend/internal/helper/jobcontract/validate.go +462 -0
- package/dist/templates/native-go/backend/internal/helper/legacy_authority_schema_linux_test.go +34 -0
- package/dist/templates/native-go/backend/internal/helper/legacy_authority_schema_test.go +6 -0
- package/dist/templates/native-go/backend/internal/helper/management.go +79 -0
- package/dist/templates/native-go/backend/internal/helper/observation.go +42 -2
- package/dist/templates/native-go/backend/internal/helper/observation_health_test.go +13 -1
- package/dist/templates/native-go/backend/internal/helper/observer_diagnostic.go +56 -10
- package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_linux_test.go +6 -0
- package/dist/templates/native-go/backend/internal/helper/observer_diagnostic_writer.go +40 -0
- package/dist/templates/native-go/backend/internal/helper/process_linux.go +3 -0
- package/dist/templates/native-go/backend/internal/helper/query_deadline_linux_test.go +115 -0
- package/dist/templates/native-go/backend/internal/helper/query_pending_diagnostic_test.go +254 -0
- package/dist/templates/native-go/backend/internal/helper/relaxed_invalidation.go +3 -0
- package/dist/templates/native-go/backend/internal/helper/s1r_actor_races_linux_test.go +23 -4
- package/dist/templates/native-go/backend/internal/helper/sdk_assembly_linux.go +51 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_attach.go +83 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_attach_contract.go +32 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_attach_test.go +184 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_authority_schema_test.go +6 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_background_authority.go +832 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_background_authority_test.go +714 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_background_invalidation.go +218 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_background_lease.go +218 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_background_snapshot.go +110 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_background_store_linux_test.go +261 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_background_store_test.go +58 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_bindings.go +60 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_data_contract.go +251 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_data_protocol.go +424 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_dispatch_absence_test.go +51 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_display.go +488 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_display_contract.go +121 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_display_test.go +459 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_grant_authority.go +559 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_grant_repository_linux.go +417 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_grant_snapshot.go +94 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_history.go +80 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_history_test.go +76 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_job.go +697 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_job_child_linux.go +309 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_job_contract.go +122 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_job_linux_test.go +159 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_job_process_linux.go +327 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_job_test.go +501 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_job_vm_linux_test.go +343 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_journal.go +77 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_journal_linux.go +244 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_journal_linux_test.go +295 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_journal_test.go +71 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_picker_test.go +50 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_process_linux.go +184 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_public_vm_linux_test.go +129 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_relaxed_invalidation.go +242 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_rpc.go +299 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_rpc_contract.go +70 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_rpc_test.go +135 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_server_linux.go +152 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_session.go +677 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_session_contract.go +72 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_session_fence_test.go +215 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_session_test.go +475 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_supervisor_frame.go +78 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_supervisor_linux.go +414 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_transport.go +79 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_transport_linux.go +82 -0
- package/dist/templates/native-go/backend/internal/helper/sdk_transport_test.go +64 -0
- package/dist/templates/native-go/backend/internal/helper/server_linux.go +3 -0
- package/dist/templates/native-go/backend/internal/helper/stale_management_linux_test.go +126 -0
- package/dist/templates/native-go/backend/internal/helper/stale_management_test.go +122 -0
- package/dist/templates/native-go/backend/internal/helper/startup_diagnostic.go +68 -0
- package/dist/templates/native-go/backend/internal/helper/startup_diagnostic_linux_test.go +126 -0
- package/dist/templates/native-go/backend/internal/helper/startup_diagnostic_test.go +117 -0
- package/dist/templates/native-go/backend/internal/helper/status.go +22 -13
- package/dist/templates/native-go/backend/internal/helper/status_test.go +45 -0
- package/dist/templates/native-go/backend/internal/helper/supervision.go +1 -1
- package/dist/templates/native-go/backend/internal/helper/supervisor_frame.go +3 -0
- package/dist/templates/native-go/backend/internal/helper/supervisor_linux.go +6 -2
- package/dist/templates/native-go/backend/internal/helper/unit_connection_test.go +22 -4
- package/dist/templates/native-go/backend/internal/helper/unit_linux.go +7 -3
- package/dist/templates/native-go/backend/internal/helper/unit_query_linux.go +5 -1
- package/dist/templates/native-go/backend/internal/helper/write_files_linux.go +7 -4
- package/dist/templates/native-go/backend/internal/helper/write_files_linux_test.go +26 -0
- package/dist/templates/native-go/dsm-package.yaml +1 -1
- package/dist/templates/native-go-background/BACKGROUND.md +111 -0
- package/dist/templates/native-go-background/backend/internal/app/background_on.go +7 -0
- package/dist/templates/native-go-background/backend/internal/app/background_routes_test.go +32 -0
- package/dist/templates/native-go-background/frontend/src/background/BackgroundPanel.vue +178 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/de.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/en.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/es.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/fr.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/it.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/ja.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/ko.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/zh-CN.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n/zh-TW.json +83 -0
- package/dist/templates/native-go-background/frontend/src/background/i18n.ts +19 -0
- package/dist/templates/native-go-background/frontend/src/background/picker-provider.ts +13 -0
- package/dist/templates/native-go-background/frontend/test/background.test.ts +179 -0
- package/dist/templates/native-go-integration/backend/internal/app/personal.go +2 -2
- package/dist/templates/native-go-integration/backend/internal/app/personal_test.go +37 -0
- package/dist/templates/native-go-vue/README.md +38 -7
- package/dist/templates/native-go-vue/frontend/DESIGN.md +15 -3
- package/dist/templates/native-go-vue/frontend/README-JOBS.md +75 -0
- package/dist/templates/native-go-vue/frontend/README-M2.md +64 -0
- package/dist/templates/native-go-vue/frontend/package-lock.json +5 -4
- package/dist/templates/native-go-vue/frontend/package.json +1 -1
- package/dist/templates/native-go-vue/frontend/src/App.vue +39 -11
- package/dist/templates/native-go-vue/frontend/src/M2App.vue +57 -0
- package/dist/templates/native-go-vue/frontend/src/SDKApp.vue +203 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/InteractiveJobForm.vue +70 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/JobsAdminPanel.vue +33 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/JobsPanel.vue +135 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/connected-provider.ts +179 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/de.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/en.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/es.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/fr.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/it.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/ja.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/ko.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/zh-CN.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n/zh-TW.json +62 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/i18n.ts +15 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/mock-provider.ts +67 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/provider.ts +60 -0
- package/dist/templates/native-go-vue/frontend/src/jobs/validate.ts +44 -0
- package/dist/templates/native-go-vue/frontend/src/m2-main.ts +21 -0
- package/dist/templates/native-go-vue/frontend/src/m2-style.css +7 -0
- package/dist/templates/native-go-vue/frontend/src/nas/M2Files.vue +94 -0
- package/dist/templates/native-go-vue/frontend/src/nas/M2PathPicker.vue +70 -0
- package/dist/templates/native-go-vue/frontend/src/nas/M2Settings.vue +69 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/de.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/en.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/es.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/fr.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/it.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/ja.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/ko.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-CN.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-TW.json +56 -0
- package/dist/templates/native-go-vue/frontend/src/nas/m2-contract.ts +33 -0
- package/dist/templates/native-go-vue/frontend/src/nas/m2-i18n.ts +24 -0
- package/dist/templates/native-go-vue/frontend/src/nas/m2-provider.ts +104 -0
- package/dist/templates/native-go-vue/frontend/src/nas/use-m2-request.ts +18 -0
- package/dist/templates/native-go-vue/frontend/src/sdk-main.ts +39 -0
- package/dist/templates/native-go-vue/frontend/src/shell/AppShell.vue +48 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/de.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/en.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/es.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/fr.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/it.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/ja.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/ko.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/zh-CN.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n/zh-TW.json +10 -0
- package/dist/templates/native-go-vue/frontend/src/shell/i18n.ts +17 -0
- package/dist/templates/native-go-vue/frontend/src/shell/pages.ts +34 -0
- package/dist/templates/native-go-vue/frontend/src/style.css +2 -6
- package/dist/templates/native-go-vue/frontend/test/apps.test.ts +21 -0
- package/dist/templates/native-go-vue/frontend/test/jobs-connected.test.ts +163 -0
- package/dist/templates/native-go-vue/frontend/test/jobs-preview.html +22 -0
- package/dist/templates/native-go-vue/frontend/test/jobs.test.ts +176 -0
- package/dist/templates/native-go-vue/frontend/test/m2-preview.html +27 -0
- package/dist/templates/native-go-vue/frontend/test/m2-provider.test.ts +87 -0
- package/dist/templates/native-go-vue/frontend/test/m2-ui.test.ts +209 -0
- package/dist/templates/native-go-vue/frontend/test/native-controls.test.ts +27 -0
- package/dist/templates/native-go-vue/frontend/test/picker-wire.test.ts +110 -0
- package/dist/templates/native-go-vue/frontend/test/shell-preview.html +19 -0
- package/dist/templates/native-go-vue/frontend/test/shell-sdk.test.ts +73 -0
- package/dist/templates/native-go-vue/frontend/test/shell-ui.test.ts +114 -0
- package/dist/templates/native-go-vue/frontend/test/shell.test.ts +45 -0
- package/dist/templates/native-go-vue/frontend/vite.config.ts +2 -2
- package/dist/validate.js +6 -3
- package/package.json +1 -1
- package/schema/synology-app.schema.json +19 -0
- package/skills/reference/create.md +4 -0
- package/templates/_daemon/go.mod +2 -0
- package/templates/_gateway/auth.go +101 -0
- package/templates/_gateway/auth_test.go +216 -0
- package/templates/_gateway/cgi.go +133 -0
- package/templates/_gateway/cgi_test.go +156 -0
- package/templates/_gateway/csrf.go +73 -0
- package/templates/_gateway/csrf_test.go +155 -0
- package/templates/_gateway/directory.go +108 -0
- package/templates/_gateway/directory_test.go +202 -0
- package/templates/_gateway/gateway.go +412 -0
- package/templates/_gateway/gateway_test.go +676 -0
- package/templates/_gateway/go.mod +3 -0
- package/templates/_gateway/groups.go +66 -0
- package/templates/_gateway/main.go +45 -0
- package/templates/binary/synology-app.yaml +1 -1
- package/templates/compose-import/compose.yaml +1 -1
- package/templates/compose-import/synology-app.yaml +1 -1
- package/templates/hello-files/compose.yaml +1 -1
- package/templates/hello-files/synology-app.yaml +1 -1
- package/templates/hello-web/compose.yaml +5 -2
- package/templates/hello-web/synology-app.yaml +4 -1
- package/templates/hello-web-page/compose.yaml +5 -2
- package/templates/hello-web-page/synology-app.yaml +4 -1
- package/templates/native-go/README.md +93 -4
- package/templates/native-go/backend/THIRD_PARTY_NOTICES.txt +3 -1
- package/templates/native-go/backend/cmd/app/main.go +17 -0
- package/templates/native-go/backend/cmd/app/sdk_main.go +60 -0
- package/templates/native-go/backend/cmd/background-worker/main.go +38 -0
- package/templates/native-go/backend/cmd/helper/main.go +10 -1
- package/templates/native-go/backend/cmd/helper/sdk_registry.go +19 -0
- package/templates/native-go/backend/go.mod +5 -3
- package/templates/native-go/backend/go.sum +4 -4
- package/templates/native-go/backend/internal/api/registry.go +17 -6
- package/templates/native-go/backend/internal/api/server.go +7 -1
- package/templates/native-go/backend/internal/app/background.go +270 -0
- package/templates/native-go/backend/internal/app/background_none.go +8 -0
- package/templates/native-go/backend/internal/app/background_owner.go +59 -0
- package/templates/native-go/backend/internal/app/background_owner_test.go +145 -0
- package/templates/native-go/backend/internal/app/background_schedule.go +74 -0
- package/templates/native-go/backend/internal/app/background_schedule_test.go +62 -0
- package/templates/native-go/backend/internal/app/background_test.go +346 -0
- package/templates/native-go/backend/internal/app/integration_state.go +16 -0
- package/templates/native-go/backend/internal/app/migrations.go +41 -0
- package/templates/native-go/backend/internal/app/migrations_test.go +41 -0
- package/templates/native-go/backend/internal/app/nas_cgi_artifact_test.go +8 -0
- package/templates/native-go/backend/internal/app/routes.go +17 -3
- package/templates/native-go/backend/internal/app/routes_test.go +20 -3
- package/templates/native-go/backend/internal/app/sdk_jobs.go +70 -0
- package/templates/native-go/backend/internal/app/sdk_jobs_test.go +116 -0
- package/templates/native-go/backend/internal/app/sdk_routes.go +174 -0
- package/templates/native-go/backend/internal/app/session_files.go +15 -0
- package/templates/native-go/backend/internal/app/session_files_test.go +26 -0
- package/templates/native-go/backend/internal/app/shared_folder.go +144 -0
- package/templates/native-go/backend/internal/app/shared_folder_test.go +158 -0
- package/templates/native-go/backend/internal/background/access.go +57 -0
- package/templates/native-go/backend/internal/background/owner_test.go +126 -0
- package/templates/native-go/backend/internal/background/runner.go +185 -0
- package/templates/native-go/backend/internal/background/runner_test.go +209 -0
- package/templates/native-go/backend/internal/background/schedule.go +300 -0
- package/templates/native-go/backend/internal/background/schedule_test.go +335 -0
- package/templates/native-go/backend/internal/background/store.go +807 -0
- package/templates/native-go/backend/internal/background/store_test.go +407 -0
- package/templates/native-go/backend/internal/datamigrate/migrate.go +191 -0
- package/templates/native-go/backend/internal/datamigrate/migrate_test.go +177 -0
- package/templates/native-go/backend/internal/filestation/browse_test.go +67 -0
- package/templates/native-go/backend/internal/filestation/client.go +251 -0
- package/templates/native-go/backend/internal/filestation/client_test.go +155 -0
- package/templates/native-go/backend/internal/filestation/doc.go +4 -0
- package/templates/native-go/backend/internal/filestation/read_file_test.go +90 -0
- package/templates/native-go/backend/internal/filestation/repository.go +207 -0
- package/templates/native-go/backend/internal/filestation/repository_test.go +104 -0
- package/templates/native-go/backend/internal/filestation/sdk_session.go +241 -0
- package/templates/native-go/backend/internal/filestation/sdk_session_test.go +183 -0
- package/templates/native-go/backend/internal/filestation/service.go +254 -0
- package/templates/native-go/backend/internal/filestation/service_test.go +94 -0
- package/templates/native-go/backend/internal/filestation/session_semantics_test.go +383 -0
- package/templates/native-go/backend/internal/filestation/write.go +72 -0
- package/templates/native-go/backend/internal/filestation/write_test.go +123 -0
- package/templates/native-go/backend/internal/helper/background_proof.go +163 -0
- package/templates/native-go/backend/internal/helper/background_proof_contract.go +46 -0
- package/templates/native-go/backend/internal/helper/background_proof_test.go +161 -0
- package/templates/native-go/backend/internal/helper/data_contract.go +3 -0
- package/templates/native-go/backend/internal/helper/data_protocol.go +3 -0
- package/templates/native-go/backend/internal/helper/data_protocol_test.go +1 -1
- package/templates/native-go/backend/internal/helper/grant_authority.go +3 -0
- package/templates/native-go/backend/internal/helper/grant_authority_test.go +1 -1
- package/templates/native-go/backend/internal/helper/grant_repository_linux.go +2 -1
- package/templates/native-go/backend/internal/helper/grant_repository_linux_test.go +4 -3
- package/templates/native-go/backend/internal/helper/grant_snapshot.go +3 -0
- package/templates/native-go/backend/internal/helper/jobcontract/bindings.go +48 -0
- package/templates/native-go/backend/internal/helper/jobcontract/contract_test.go +436 -0
- package/templates/native-go/backend/internal/helper/jobcontract/decode.go +185 -0
- package/templates/native-go/backend/internal/helper/jobcontract/digest.go +55 -0
- package/templates/native-go/backend/internal/helper/jobcontract/registry.go +85 -0
- package/templates/native-go/backend/internal/helper/jobcontract/registry_test.go +73 -0
- package/templates/native-go/backend/internal/helper/jobcontract/result.go +53 -0
- package/templates/native-go/backend/internal/helper/jobcontract/stub.go +8 -0
- package/templates/native-go/backend/internal/helper/jobcontract/testdata/input-digests.json +127 -0
- package/templates/native-go/backend/internal/helper/jobcontract/types.go +234 -0
- package/templates/native-go/backend/internal/helper/jobcontract/types_test.go +29 -0
- package/templates/native-go/backend/internal/helper/jobcontract/validate.go +462 -0
- package/templates/native-go/backend/internal/helper/legacy_authority_schema_linux_test.go +34 -0
- package/templates/native-go/backend/internal/helper/legacy_authority_schema_test.go +6 -0
- package/templates/native-go/backend/internal/helper/management.go +79 -0
- package/templates/native-go/backend/internal/helper/observation.go +42 -2
- package/templates/native-go/backend/internal/helper/observation_health_test.go +13 -1
- package/templates/native-go/backend/internal/helper/observer_diagnostic.go +56 -10
- package/templates/native-go/backend/internal/helper/observer_diagnostic_linux_test.go +6 -0
- package/templates/native-go/backend/internal/helper/observer_diagnostic_writer.go +40 -0
- package/templates/native-go/backend/internal/helper/process_linux.go +3 -0
- package/templates/native-go/backend/internal/helper/query_deadline_linux_test.go +115 -0
- package/templates/native-go/backend/internal/helper/query_pending_diagnostic_test.go +254 -0
- package/templates/native-go/backend/internal/helper/relaxed_invalidation.go +3 -0
- package/templates/native-go/backend/internal/helper/s1r_actor_races_linux_test.go +23 -4
- package/templates/native-go/backend/internal/helper/sdk_assembly_linux.go +51 -0
- package/templates/native-go/backend/internal/helper/sdk_attach.go +83 -0
- package/templates/native-go/backend/internal/helper/sdk_attach_contract.go +32 -0
- package/templates/native-go/backend/internal/helper/sdk_attach_test.go +184 -0
- package/templates/native-go/backend/internal/helper/sdk_authority_schema_test.go +6 -0
- package/templates/native-go/backend/internal/helper/sdk_background_authority.go +832 -0
- package/templates/native-go/backend/internal/helper/sdk_background_authority_test.go +714 -0
- package/templates/native-go/backend/internal/helper/sdk_background_invalidation.go +218 -0
- package/templates/native-go/backend/internal/helper/sdk_background_lease.go +218 -0
- package/templates/native-go/backend/internal/helper/sdk_background_snapshot.go +110 -0
- package/templates/native-go/backend/internal/helper/sdk_background_store_linux_test.go +261 -0
- package/templates/native-go/backend/internal/helper/sdk_background_store_test.go +58 -0
- package/templates/native-go/backend/internal/helper/sdk_bindings.go +60 -0
- package/templates/native-go/backend/internal/helper/sdk_data_contract.go +251 -0
- package/templates/native-go/backend/internal/helper/sdk_data_protocol.go +424 -0
- package/templates/native-go/backend/internal/helper/sdk_dispatch_absence_test.go +51 -0
- package/templates/native-go/backend/internal/helper/sdk_display.go +488 -0
- package/templates/native-go/backend/internal/helper/sdk_display_contract.go +121 -0
- package/templates/native-go/backend/internal/helper/sdk_display_test.go +459 -0
- package/templates/native-go/backend/internal/helper/sdk_grant_authority.go +559 -0
- package/templates/native-go/backend/internal/helper/sdk_grant_repository_linux.go +417 -0
- package/templates/native-go/backend/internal/helper/sdk_grant_snapshot.go +94 -0
- package/templates/native-go/backend/internal/helper/sdk_history.go +80 -0
- package/templates/native-go/backend/internal/helper/sdk_history_test.go +76 -0
- package/templates/native-go/backend/internal/helper/sdk_job.go +697 -0
- package/templates/native-go/backend/internal/helper/sdk_job_child_linux.go +309 -0
- package/templates/native-go/backend/internal/helper/sdk_job_contract.go +122 -0
- package/templates/native-go/backend/internal/helper/sdk_job_linux_test.go +159 -0
- package/templates/native-go/backend/internal/helper/sdk_job_process_linux.go +327 -0
- package/templates/native-go/backend/internal/helper/sdk_job_test.go +501 -0
- package/templates/native-go/backend/internal/helper/sdk_job_vm_linux_test.go +343 -0
- package/templates/native-go/backend/internal/helper/sdk_journal.go +77 -0
- package/templates/native-go/backend/internal/helper/sdk_journal_linux.go +244 -0
- package/templates/native-go/backend/internal/helper/sdk_journal_linux_test.go +295 -0
- package/templates/native-go/backend/internal/helper/sdk_journal_test.go +71 -0
- package/templates/native-go/backend/internal/helper/sdk_picker_test.go +50 -0
- package/templates/native-go/backend/internal/helper/sdk_process_linux.go +184 -0
- package/templates/native-go/backend/internal/helper/sdk_public_vm_linux_test.go +129 -0
- package/templates/native-go/backend/internal/helper/sdk_relaxed_invalidation.go +242 -0
- package/templates/native-go/backend/internal/helper/sdk_rpc.go +299 -0
- package/templates/native-go/backend/internal/helper/sdk_rpc_contract.go +70 -0
- package/templates/native-go/backend/internal/helper/sdk_rpc_test.go +135 -0
- package/templates/native-go/backend/internal/helper/sdk_server_linux.go +152 -0
- package/templates/native-go/backend/internal/helper/sdk_session.go +677 -0
- package/templates/native-go/backend/internal/helper/sdk_session_contract.go +72 -0
- package/templates/native-go/backend/internal/helper/sdk_session_fence_test.go +215 -0
- package/templates/native-go/backend/internal/helper/sdk_session_test.go +475 -0
- package/templates/native-go/backend/internal/helper/sdk_supervisor_frame.go +78 -0
- package/templates/native-go/backend/internal/helper/sdk_supervisor_linux.go +414 -0
- package/templates/native-go/backend/internal/helper/sdk_transport.go +79 -0
- package/templates/native-go/backend/internal/helper/sdk_transport_linux.go +82 -0
- package/templates/native-go/backend/internal/helper/sdk_transport_test.go +64 -0
- package/templates/native-go/backend/internal/helper/server_linux.go +3 -0
- package/templates/native-go/backend/internal/helper/stale_management_linux_test.go +126 -0
- package/templates/native-go/backend/internal/helper/stale_management_test.go +122 -0
- package/templates/native-go/backend/internal/helper/startup_diagnostic.go +68 -0
- package/templates/native-go/backend/internal/helper/startup_diagnostic_linux_test.go +126 -0
- package/templates/native-go/backend/internal/helper/startup_diagnostic_test.go +117 -0
- package/templates/native-go/backend/internal/helper/status.go +22 -13
- package/templates/native-go/backend/internal/helper/status_test.go +45 -0
- package/templates/native-go/backend/internal/helper/supervision.go +1 -1
- package/templates/native-go/backend/internal/helper/supervisor_frame.go +3 -0
- package/templates/native-go/backend/internal/helper/supervisor_linux.go +6 -2
- package/templates/native-go/backend/internal/helper/unit_connection_test.go +22 -4
- package/templates/native-go/backend/internal/helper/unit_linux.go +7 -3
- package/templates/native-go/backend/internal/helper/unit_query_linux.go +5 -1
- package/templates/native-go/backend/internal/helper/write_files_linux.go +7 -4
- package/templates/native-go/backend/internal/helper/write_files_linux_test.go +26 -0
- package/templates/native-go/dsm-package.yaml +1 -1
- package/templates/native-go-background/BACKGROUND.md +111 -0
- package/templates/native-go-background/backend/internal/app/background_on.go +7 -0
- package/templates/native-go-background/backend/internal/app/background_routes_test.go +32 -0
- package/templates/native-go-background/frontend/src/background/BackgroundPanel.vue +178 -0
- package/templates/native-go-background/frontend/src/background/i18n/de.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n/en.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n/es.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n/fr.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n/it.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n/ja.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n/ko.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n/zh-CN.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n/zh-TW.json +83 -0
- package/templates/native-go-background/frontend/src/background/i18n.ts +19 -0
- package/templates/native-go-background/frontend/src/background/picker-provider.ts +13 -0
- package/templates/native-go-background/frontend/test/background.test.ts +179 -0
- package/templates/native-go-integration/backend/internal/app/personal.go +2 -2
- package/templates/native-go-integration/backend/internal/app/personal_test.go +37 -0
- package/templates/native-go-vue/README.md +38 -7
- package/templates/native-go-vue/frontend/DESIGN.md +15 -3
- package/templates/native-go-vue/frontend/README-JOBS.md +75 -0
- package/templates/native-go-vue/frontend/README-M2.md +64 -0
- package/templates/native-go-vue/frontend/package-lock.json +5 -4
- package/templates/native-go-vue/frontend/package.json +1 -1
- package/templates/native-go-vue/frontend/src/App.vue +39 -11
- package/templates/native-go-vue/frontend/src/M2App.vue +57 -0
- package/templates/native-go-vue/frontend/src/SDKApp.vue +203 -0
- package/templates/native-go-vue/frontend/src/jobs/InteractiveJobForm.vue +70 -0
- package/templates/native-go-vue/frontend/src/jobs/JobsAdminPanel.vue +33 -0
- package/templates/native-go-vue/frontend/src/jobs/JobsPanel.vue +135 -0
- package/templates/native-go-vue/frontend/src/jobs/connected-provider.ts +179 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/de.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/en.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/es.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/fr.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/it.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/ja.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/ko.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/zh-CN.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n/zh-TW.json +62 -0
- package/templates/native-go-vue/frontend/src/jobs/i18n.ts +15 -0
- package/templates/native-go-vue/frontend/src/jobs/mock-provider.ts +67 -0
- package/templates/native-go-vue/frontend/src/jobs/provider.ts +60 -0
- package/templates/native-go-vue/frontend/src/jobs/validate.ts +44 -0
- package/templates/native-go-vue/frontend/src/m2-main.ts +21 -0
- package/templates/native-go-vue/frontend/src/m2-style.css +7 -0
- package/templates/native-go-vue/frontend/src/nas/M2Files.vue +94 -0
- package/templates/native-go-vue/frontend/src/nas/M2PathPicker.vue +70 -0
- package/templates/native-go-vue/frontend/src/nas/M2Settings.vue +69 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/de.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/en.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/es.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/fr.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/it.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/ja.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/ko.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-CN.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/i18n/m2/zh-TW.json +56 -0
- package/templates/native-go-vue/frontend/src/nas/m2-contract.ts +33 -0
- package/templates/native-go-vue/frontend/src/nas/m2-i18n.ts +24 -0
- package/templates/native-go-vue/frontend/src/nas/m2-provider.ts +104 -0
- package/templates/native-go-vue/frontend/src/nas/use-m2-request.ts +18 -0
- package/templates/native-go-vue/frontend/src/sdk-main.ts +39 -0
- package/templates/native-go-vue/frontend/src/shell/AppShell.vue +48 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/de.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/en.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/es.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/fr.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/it.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/ja.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/ko.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/zh-CN.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n/zh-TW.json +10 -0
- package/templates/native-go-vue/frontend/src/shell/i18n.ts +17 -0
- package/templates/native-go-vue/frontend/src/shell/pages.ts +34 -0
- package/templates/native-go-vue/frontend/src/style.css +2 -6
- package/templates/native-go-vue/frontend/test/apps.test.ts +21 -0
- package/templates/native-go-vue/frontend/test/jobs-connected.test.ts +163 -0
- package/templates/native-go-vue/frontend/test/jobs-preview.html +22 -0
- package/templates/native-go-vue/frontend/test/jobs.test.ts +176 -0
- package/templates/native-go-vue/frontend/test/m2-preview.html +27 -0
- package/templates/native-go-vue/frontend/test/m2-provider.test.ts +87 -0
- package/templates/native-go-vue/frontend/test/m2-ui.test.ts +209 -0
- package/templates/native-go-vue/frontend/test/native-controls.test.ts +27 -0
- package/templates/native-go-vue/frontend/test/picker-wire.test.ts +110 -0
- package/templates/native-go-vue/frontend/test/shell-preview.html +19 -0
- package/templates/native-go-vue/frontend/test/shell-sdk.test.ts +73 -0
- package/templates/native-go-vue/frontend/test/shell-ui.test.ts +114 -0
- package/templates/native-go-vue/frontend/test/shell.test.ts +45 -0
- package/templates/native-go-vue/frontend/vite.config.ts +2 -2
|
@@ -0,0 +1,209 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {expect,test,vi} from 'vitest'
|
|
3
|
+
import {createApp,h,nextTick} from 'vue'
|
|
4
|
+
import {AppRoot} from '@synopackageland/ui'
|
|
5
|
+
import {mount} from '../src/m2-main'
|
|
6
|
+
import M2PathPicker from '../src/nas/M2PathPicker.vue'
|
|
7
|
+
import {M2_MAX_BYTES,M2ProviderKey,type M2Provider} from '../src/nas/m2-contract'
|
|
8
|
+
import {m2Copy,m2Catalogs,m2ErrorCopy} from '../src/nas/m2-i18n'
|
|
9
|
+
import {format} from '../src/nas/i18n'
|
|
10
|
+
import {shellCopy} from '../src/shell/i18n'
|
|
11
|
+
import type {Context} from '../src/context'
|
|
12
|
+
vi.stubGlobal('URL',Object.assign(URL,{createObjectURL:vi.fn(()=> 'blob:local-test'),revokeObjectURL:vi.fn()}))
|
|
13
|
+
const id='a'.repeat(64)
|
|
14
|
+
async function settle(){for(let i=0;i<20;i++){await Promise.resolve();await nextTick()}await new Promise(r=>setTimeout(r,25))}
|
|
15
|
+
function click(root:HTMLElement,text:string){const b=[...root.querySelectorAll('button')].find(b=>b.textContent?.trim()===text||b.getAttribute('aria-label')===text);expect(b,text).toBeTruthy();b!.click()}
|
|
16
|
+
function fixture(locale='en'){
|
|
17
|
+
let user='normal1',selections:any[]=[],failure=''
|
|
18
|
+
const reopen:Array<()=>void>=[]
|
|
19
|
+
const call=vi.fn(async(method:string,args?:Record<string,unknown>)=>{
|
|
20
|
+
if(failure&&method.startsWith('nas.'))throw {code:failure,message:'Cookie=secret /private'}
|
|
21
|
+
if(method==='app.status')return {package:'SamePackage',version:'1',user,admin:false}
|
|
22
|
+
if(method==='nas.session.status')return {mode:'filestation-session',interactive:'session-required',background:'unavailable',validation:'local-only'}
|
|
23
|
+
if(method==='nas.discovery')return {files:args?.folder?[]:null,shares:args?.folder?undefined:[{name:'Home',path:'/home',isdir:true}],total:args?.folder?0:1,offset:0}
|
|
24
|
+
if(method==='nas.grants.list')return selections
|
|
25
|
+
if(method==='nas.grants.confirm'){const s={grantId:id,folder:args!.folder,writable:args!.writable};selections.push(s);return s}
|
|
26
|
+
if(method==='nas.grants.revoke'){selections=[];return {revoked:true}}
|
|
27
|
+
if(method==='nas.files.list')return {files:[{name:'notes.txt',path:'/home/notes.txt',isdir:false,additional:{size:3}}],total:1,offset:0}
|
|
28
|
+
if(method==='nas.files.stat')return {name:'notes.txt',path:'/home/notes.txt',isdir:false,additional:{size:3}}
|
|
29
|
+
if(method==='nas.files.read')return {data:'YWJj',bytes:3}
|
|
30
|
+
if(method==='nas.files.create')return {created:true}
|
|
31
|
+
if(method==='nas.files.write')return {written:true}
|
|
32
|
+
throw {code:'unknown_method'}
|
|
33
|
+
})
|
|
34
|
+
const ctx={locale,call,reopen(fn:()=>void){reopen.push(fn);return ()=>{}}} as Context
|
|
35
|
+
const root=document.createElement('div');document.body.append(root);const app=mount(root,ctx)
|
|
36
|
+
return {root,app,call,reopen,setUser(value:string){user=value},fail(code:string){failure=code},close(){app.unmount();root.remove()}}
|
|
37
|
+
}
|
|
38
|
+
test('the File Station build uses the same DSM sidebar with a status page',async()=>{
|
|
39
|
+
const f=fixture(),copy=shellCopy('en')
|
|
40
|
+
try{
|
|
41
|
+
await settle()
|
|
42
|
+
const labels=[...f.root.querySelectorAll<HTMLElement>('[data-nav-item]')].map(node=>node.textContent?.trim())
|
|
43
|
+
expect(labels).toEqual([copy.status,copy.folders])
|
|
44
|
+
expect(f.root.querySelector('h1')?.textContent?.trim()).toBe(copy.status)
|
|
45
|
+
const folders=[...f.root.querySelectorAll<HTMLElement>('[data-nav-item]')].find(node=>node.textContent?.trim()===copy.folders)!
|
|
46
|
+
folders.click();await settle()
|
|
47
|
+
expect(f.root.querySelector('h1')?.textContent?.trim()).toBe(copy.folders)
|
|
48
|
+
expect(f.root.textContent).toContain(m2Copy('en').ownPermissions)
|
|
49
|
+
}finally{f.close()}
|
|
50
|
+
})
|
|
51
|
+
|
|
52
|
+
test('real session routes drive browse, writable consent, file actions and revoke without helper approval',async()=>{
|
|
53
|
+
const f=fixture(),copy=m2Copy('en')
|
|
54
|
+
try{
|
|
55
|
+
await settle();expect(f.root.textContent).toContain(copy.ownPermissions);expect(f.root.textContent).toContain(copy.unsupported)
|
|
56
|
+
click(f.root,copy.choose);await settle();click(f.root,'Select Home');await settle()
|
|
57
|
+
const checkbox=f.root.querySelector<HTMLElement>('[role=checkbox]')!;checkbox.click()
|
|
58
|
+
click(f.root,copy.confirm);await settle()
|
|
59
|
+
expect(f.call).toHaveBeenCalledWith('nas.grants.confirm',{folder:'/home',writable:true})
|
|
60
|
+
click(f.root,copy.openFiles);await settle();click(f.root,copy.inspect);await settle()
|
|
61
|
+
expect(f.root.textContent).toContain('3');click(f.root,copy.read);await settle()
|
|
62
|
+
expect(f.root.textContent).toContain(copy.readComplete)
|
|
63
|
+
const labelled=(text:string)=>f.root.querySelector<HTMLElement>(`#${[...f.root.querySelectorAll('label')].find(l=>l.textContent?.startsWith(text))!.htmlFor}`)
|
|
64
|
+
expect(labelled(copy.folderName)).toBe(f.root.querySelector('input[data-folder-name]'));expect(labelled(copy.uploadFile)).toBe(f.root.querySelector('input[type=file]'))
|
|
65
|
+
const input=f.root.querySelector<HTMLInputElement>('input[data-folder-name]')!;input.value='new';input.dispatchEvent(new Event('input',{bubbles:true}));await settle()
|
|
66
|
+
click(f.root,copy.create);await settle();expect(f.call).toHaveBeenCalledWith('nas.files.create',{grantId:id,relative:'',name:'new'})
|
|
67
|
+
const upload=f.root.querySelector<HTMLInputElement>('input[type=file]')!
|
|
68
|
+
Object.defineProperty(upload,'files',{value:[{name:'note.bin',size:3,arrayBuffer:async()=>new Uint8Array([0,1,255]).buffer}]})
|
|
69
|
+
upload.dispatchEvent(new Event('change',{bubbles:true}));await settle();click(f.root,copy.upload);await settle()
|
|
70
|
+
expect(f.call).toHaveBeenCalledWith('nas.files.write',{grantId:id,relative:'',name:'note.bin',data:'AAH/'})
|
|
71
|
+
click(f.root,copy.revoke);await settle();click(f.root,copy.confirmRevoke);await settle()
|
|
72
|
+
expect(f.root.textContent).toContain(copy.emptySelections)
|
|
73
|
+
expect(f.call.mock.calls.some(([m])=>/prepare|stream|publish|integration|root/.test(m))).toBe(false)
|
|
74
|
+
}finally{f.close()}
|
|
75
|
+
})
|
|
76
|
+
test('nine catalogs contain matching placeholders and distinct recovery copy for each safe failure',async()=>{
|
|
77
|
+
expect(Object.keys(m2Catalogs)).toHaveLength(9)
|
|
78
|
+
for(const [locale,copy]of Object.entries(m2Catalogs)){
|
|
79
|
+
expect(Object.keys(copy).sort()).toEqual(Object.keys(m2Catalogs.en).sort())
|
|
80
|
+
for(const key of Object.keys(copy)as Array<keyof typeof copy>){expect(copy[key].trim()).not.toBe('');expect(copy[key].match(/\{\w+\}/g)??[]).toEqual(m2Catalogs.en[key].match(/\{\w+\}/g)??[])}
|
|
81
|
+
const messages=['session_required','denied','not_found','conflict','commit-uncertain'].map(code=>m2ErrorCopy({code,message:'secret'},copy))
|
|
82
|
+
expect(new Set(messages).size).toBe(5)
|
|
83
|
+
const f=fixture(locale)
|
|
84
|
+
try{await settle();expect(f.root.textContent).toContain(copy.ownPermissions);f.fail('session_required');click(f.root,copy.refresh);await settle();expect(f.root.textContent).toContain(copy.errorSession);expect(f.root.textContent).not.toContain('Cookie=secret')}
|
|
85
|
+
finally{f.close()}
|
|
86
|
+
}
|
|
87
|
+
})
|
|
88
|
+
test('account change discards selections and pending dialogs until remount; two mounts isolate dialogs',async()=>{
|
|
89
|
+
const a=fixture(),b=fixture(),copy=m2Copy('en')
|
|
90
|
+
try{
|
|
91
|
+
await settle();click(a.root,copy.choose);click(b.root,copy.choose);await settle()
|
|
92
|
+
const ids=[...document.querySelectorAll('[id]')].map(n=>n.id).filter(Boolean);expect(new Set(ids).size).toBe(ids.length)
|
|
93
|
+
expect(a.root.querySelector('[role=dialog]')?.hasAttribute('aria-modal')).toBe(false)
|
|
94
|
+
a.setUser('normal2');a.reopen.forEach(fn=>fn());await settle()
|
|
95
|
+
expect(a.root.textContent).toContain(copy.accountChanged);expect(a.root.querySelector('[role=dialog]')).toBeNull()
|
|
96
|
+
expect(b.root.querySelector('[role=dialog]')).toBeTruthy()
|
|
97
|
+
}finally{a.close();b.close()}
|
|
98
|
+
})
|
|
99
|
+
|
|
100
|
+
test('read-only selection hides mutations; cancellation never confirms and errors never auto-retry',async()=>{
|
|
101
|
+
const f=fixture(),copy=m2Copy('en')
|
|
102
|
+
try{
|
|
103
|
+
await settle();click(f.root,copy.choose);await settle();click(f.root,'Select Home');click(f.root,copy.cancel);await settle()
|
|
104
|
+
expect(f.call.mock.calls.some(([m])=>m==='nas.grants.confirm')).toBe(false)
|
|
105
|
+
click(f.root,copy.choose);await settle();click(f.root,'Select Home');await settle();click(f.root,copy.confirm);await settle()
|
|
106
|
+
expect(f.call).toHaveBeenCalledWith('nas.grants.confirm',{folder:'/home',writable:false})
|
|
107
|
+
click(f.root,copy.openFiles);await settle()
|
|
108
|
+
expect(f.root.querySelector('input[type=file]')).toBeNull();expect(f.root.querySelector('input[data-folder-name]')).toBeNull()
|
|
109
|
+
f.fail('not_found');click(f.root,copy.inspect);await settle();expect(f.root.textContent).toContain(copy.errorNotFound)
|
|
110
|
+
expect(f.call.mock.calls.filter(([m])=>m==='nas.files.stat')).toHaveLength(1)
|
|
111
|
+
}finally{f.close()}
|
|
112
|
+
})
|
|
113
|
+
test('late discovery is discarded on unmount; a failed refresh does not forget the original account binding',async()=>{
|
|
114
|
+
const f=fixture(),copy=m2Copy('en')
|
|
115
|
+
try{
|
|
116
|
+
await settle()
|
|
117
|
+
const original=f.call.getMockImplementation()!
|
|
118
|
+
f.call.mockImplementation(async(method,args)=>{if(method==='app.status')throw {code:'session_required'};return original(method,args)})
|
|
119
|
+
f.reopen.forEach(fn=>fn());await settle()
|
|
120
|
+
f.call.mockImplementation(original);f.setUser('normal2');f.reopen.forEach(fn=>fn());await settle()
|
|
121
|
+
expect(f.root.textContent).toContain(copy.accountChanged)
|
|
122
|
+
}finally{f.close()}
|
|
123
|
+
const g=fixture()
|
|
124
|
+
await settle()
|
|
125
|
+
let release!:(value:unknown)=>void
|
|
126
|
+
const original=g.call.getMockImplementation()!
|
|
127
|
+
g.call.mockImplementation(async(method,args)=>method==='nas.discovery'?new Promise(r=>release=r):original(method,args))
|
|
128
|
+
click(g.root,copy.choose);await settle();g.app.unmount()
|
|
129
|
+
release({files:null,shares:[{name:'private',path:'/private',isdir:true}],total:1,offset:0});await settle()
|
|
130
|
+
expect(g.root.childElementCount).toBe(0);g.root.remove()
|
|
131
|
+
})
|
|
132
|
+
|
|
133
|
+
test('pending upload blocks competing folder controls and duplicate submission; uncertain outcome is actionable',async()=>{
|
|
134
|
+
const f=fixture(),copy=m2Copy('en')
|
|
135
|
+
try{
|
|
136
|
+
await settle();click(f.root,copy.choose);await settle();click(f.root,'Select Home');await settle()
|
|
137
|
+
const checkbox=f.root.querySelector<HTMLElement>('[role=checkbox]')!;checkbox.click()
|
|
138
|
+
click(f.root,copy.confirm);await settle();click(f.root,copy.openFiles);await settle()
|
|
139
|
+
const upload=f.root.querySelector<HTMLInputElement>('input[type=file]')!
|
|
140
|
+
Object.defineProperty(upload,'files',{value:[{name:'note.bin',size:3,arrayBuffer:async()=>new Uint8Array([0,1,255]).buffer}]})
|
|
141
|
+
upload.dispatchEvent(new Event('change',{bubbles:true}));await settle()
|
|
142
|
+
let reject!:(error:unknown)=>void
|
|
143
|
+
const original=f.call.getMockImplementation()!
|
|
144
|
+
f.call.mockImplementation(async(method,args)=>method==='nas.files.write'?new Promise((_resolve,r)=>reject=r):original(method,args))
|
|
145
|
+
click(f.root,copy.upload);click(f.root,copy.upload);await settle()
|
|
146
|
+
for(const button of [...f.root.querySelectorAll('button')].filter(b=>[copy.choose,copy.revoke,copy.refresh,copy.openFiles].includes(b.textContent?.trim()??'')))expect(button.disabled).toBe(true)
|
|
147
|
+
reject({code:'commit-uncertain'});await settle()
|
|
148
|
+
expect(f.root.textContent).toContain(copy.errorUncertain)
|
|
149
|
+
expect(f.call.mock.calls.filter(([m])=>m==='nas.files.write')).toHaveLength(1)
|
|
150
|
+
expect([...f.root.querySelectorAll('button')].find(b=>b.textContent?.trim()===copy.upload)?.disabled).toBe(true)
|
|
151
|
+
}finally{f.close()}
|
|
152
|
+
})
|
|
153
|
+
|
|
154
|
+
function mountPicker(consent?:string){
|
|
155
|
+
const provider={discovery:async()=>({entries:[],nextOffset:undefined})} as unknown as M2Provider
|
|
156
|
+
const root=document.createElement('div');document.body.append(root)
|
|
157
|
+
const app=createApp({render:()=>h(AppRoot,{},()=>h(M2PathPicker,{open:true,locale:'en',consent}))})
|
|
158
|
+
app.provide(M2ProviderKey,provider);app.config.idPrefix=`picker-${Math.random()}`;app.mount(root)
|
|
159
|
+
return {root,close(){app.unmount();root.remove()}}
|
|
160
|
+
}
|
|
161
|
+
test('the picker shows the caller-provided consent copy and keeps the M2 copy by default',async()=>{
|
|
162
|
+
const custom=mountPicker('Read once with your DSM login.');await settle()
|
|
163
|
+
expect(custom.root.textContent).toContain('Read once with your DSM login.');expect(custom.root.textContent).not.toContain(m2Copy('en').consent);custom.close()
|
|
164
|
+
const plain=mountPicker();await settle()
|
|
165
|
+
expect(plain.root.textContent).toContain(m2Copy('en').consent);plain.close()
|
|
166
|
+
})
|
|
167
|
+
|
|
168
|
+
async function openFiles(f:ReturnType<typeof fixture>,locale='en'){
|
|
169
|
+
const copy=m2Copy(locale)
|
|
170
|
+
await settle()
|
|
171
|
+
// In ko the navigation item shares the Choose button's label; the page button is the last match.
|
|
172
|
+
;[...f.root.querySelectorAll('button')].filter(b=>b.textContent?.trim()===copy.choose).at(-1)!.click();await settle();click(f.root,format(copy.select,'Home'));await settle()
|
|
173
|
+
f.root.querySelector<HTMLElement>('[role=checkbox]')!.click()
|
|
174
|
+
click(f.root,copy.confirm);await settle();click(f.root,copy.openFiles);await settle()
|
|
175
|
+
return copy
|
|
176
|
+
}
|
|
177
|
+
// UI FilePicker "Choose files" per locale, written as escapes: template sources keep non-English text in catalogs only.
|
|
178
|
+
const chooseFiles:Record<string,string>={en:'Choose files','zh-TW':'\u9078\u64c7\u6a94\u6848','zh-CN':'\u9009\u62e9\u6587\u4ef6',ja:'\u30d5\u30a1\u30a4\u30eb\u3092\u9078\u629e',ko:'\ud30c\uc77c \uc120\ud0dd',de:'Dateien ausw\u00e4hlen',fr:'Choisir des fichiers',es:'Elegir archivos',it:'Scegli file'}
|
|
179
|
+
test.each(Object.keys(chooseFiles))('upload uses the compact UI FilePicker in %s',async locale=>{
|
|
180
|
+
const f=fixture(locale)
|
|
181
|
+
try{
|
|
182
|
+
const copy=await openFiles(f,locale)
|
|
183
|
+
expect(f.root.textContent).toContain(chooseFiles[locale])
|
|
184
|
+
expect(f.root.querySelectorAll('input[type=file]')).toHaveLength(1)
|
|
185
|
+
const upload=f.root.querySelector<HTMLInputElement>('input[type=file]')!
|
|
186
|
+
expect(upload.multiple).toBe(false)
|
|
187
|
+
expect(f.root.querySelector(`label[for="${upload.id}"]`)?.textContent).toContain(copy.uploadFile)
|
|
188
|
+
}finally{f.close()}
|
|
189
|
+
})
|
|
190
|
+
test('an oversize file is refused with the limit message and is never uploaded',async()=>{
|
|
191
|
+
const f=fixture(),copy=m2Copy('en')
|
|
192
|
+
try{
|
|
193
|
+
await openFiles(f)
|
|
194
|
+
const upload=f.root.querySelector<HTMLInputElement>('input[type=file]')!
|
|
195
|
+
Object.defineProperty(upload,'files',{value:[new File([new Uint8Array(M2_MAX_BYTES+1)],'big.bin')]})
|
|
196
|
+
upload.dispatchEvent(new Event('change',{bubbles:true}));await settle()
|
|
197
|
+
expect(f.root.textContent).toContain(m2ErrorCopy({code:'limit-exceeded'},copy))
|
|
198
|
+
expect([...f.root.querySelectorAll('button')].find(b=>b.textContent?.trim()===copy.upload)?.disabled).toBe(true)
|
|
199
|
+
expect(f.call.mock.calls.some(([m])=>m==='nas.files.write')).toBe(false)
|
|
200
|
+
}finally{f.close()}
|
|
201
|
+
})
|
|
202
|
+
test('a downloaded read result is a UI Link with the file name',async()=>{
|
|
203
|
+
const f=fixture(),copy=m2Copy('en')
|
|
204
|
+
try{
|
|
205
|
+
await openFiles(f);click(f.root,copy.read);await settle()
|
|
206
|
+
const link=f.root.querySelector<HTMLAnchorElement>('a[download]')!
|
|
207
|
+
expect(link.textContent).toContain(copy.download);expect(link.download).toBe('notes.txt');expect(link.getAttribute('href')).toMatch(/^blob:/)
|
|
208
|
+
}finally{f.close()}
|
|
209
|
+
})
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import {readdirSync,readFileSync} from 'node:fs'
|
|
2
|
+
import {join} from 'node:path'
|
|
3
|
+
import {expect,test} from 'vitest'
|
|
4
|
+
const root=join(__dirname,'../src')
|
|
5
|
+
const sources=(dir:string):string[]=>readdirSync(dir,{withFileTypes:true}).flatMap(e=>e.isDirectory()?sources(join(dir,e.name)):e.name.endsWith('.vue')?[join(dir,e.name)]:[])
|
|
6
|
+
const template=(file:string)=>readFileSync(file,'utf8').replace(/<script[\s\S]*?<\/script>/g,'').replace(/<style[\s\S]*?<\/style>/g,'')
|
|
7
|
+
const uses=(file:string,tag:string)=>new RegExp(`<${tag}[\\s>/]`).test(template(file))
|
|
8
|
+
// The template ships @synopackageland/ui controls only; a native form control
|
|
9
|
+
// means a missing UI component, which is a UI gap to report, not to paper over.
|
|
10
|
+
test.each(['select','option','textarea','label','input','a'])('no vue template renders a native <%s>',tag=>{
|
|
11
|
+
expect(sources(root).filter(file=>uses(file,tag))).toEqual([])
|
|
12
|
+
})
|
|
13
|
+
// Form direction and the control id come from the UI: `direction="auto"` stacks labels in a
|
|
14
|
+
// narrow package window, and a control inside a FormItem binds its id/ARIA by itself.
|
|
15
|
+
test('FormItem controls rely on the UI auto layout and id binding',()=>{
|
|
16
|
+
const items=sources(root).filter(file=>uses(file,'FormItem'))
|
|
17
|
+
expect(items.length).toBeGreaterThan(0)
|
|
18
|
+
expect(items.filter(file=>/direction="vertical"|field\.controlId|#default="\{field\}"/.test(template(file)))).toEqual([])
|
|
19
|
+
})
|
|
20
|
+
// UI built-in text follows the Provider locale (nine locales), so every app root sets it once and no
|
|
21
|
+
// component repeats a per-control locale or download label.
|
|
22
|
+
test('app roots set the UI locale once and components do not translate UI text themselves',()=>{
|
|
23
|
+
const roots=sources(root).filter(file=>uses(file,'AppRoot'))
|
|
24
|
+
expect(roots.length).toBeGreaterThan(0)
|
|
25
|
+
expect(roots.filter(file=>!uses(file,'ConfigProvider'))).toEqual([])
|
|
26
|
+
expect(sources(root).filter(file=>/download-label|external-label|<FilePicker[^>]*:locale=/.test(template(file)))).toEqual([])
|
|
27
|
+
})
|
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {expect,test,vi} from 'vitest'
|
|
3
|
+
import {nextTick} from 'vue'
|
|
4
|
+
import {mount} from '../src/sdk-main'
|
|
5
|
+
import {jobCopy,jobCatalogs} from '../src/jobs/i18n'
|
|
6
|
+
import {m2Copy} from '../src/nas/m2-i18n'
|
|
7
|
+
import type {Context} from '../src/context'
|
|
8
|
+
const id=(c:string)=>c.repeat(64)
|
|
9
|
+
async function settle(){for(let i=0;i<35;i++){await Promise.resolve();await nextTick()}await new Promise(r=>setTimeout(r,25))}
|
|
10
|
+
function click(root:HTMLElement,text:string){const b=[...root.querySelectorAll('button')].find(b=>b.textContent?.trim()===text||b.getAttribute('aria-label')===text);expect(b,text).toBeTruthy();b!.click()}
|
|
11
|
+
function fixture(locale='en'){
|
|
12
|
+
let state='running',reason='',lost=false,user='normal1',deny=false
|
|
13
|
+
const reopen:Array<()=>void>=[]
|
|
14
|
+
const wire=()=>({jobId:id('b'),revision:2,mode:'interactive',state,safeReason:reason||undefined,progress:{completedUnits:state==='succeeded'?1:0,totalUnits:1},drained:state==='succeeded',effectsUnknown:false})
|
|
15
|
+
const call=vi.fn(async(method:string,args?:Record<string,unknown>)=>{
|
|
16
|
+
if(method==='app.status')return {package:'t0090',version:'1',user,admin:false}
|
|
17
|
+
if(method==='integration.status')return {state:'ready',packageRunning:'running',scheduler:'ready'}
|
|
18
|
+
if(method==='personal.list')return {items:[]}
|
|
19
|
+
if(method==='jobs.status')return {revision:2,availability:'session-required',workers:[{id:'inspect',revision:7,actions:['nas.files.list','nas.files.stat','nas.files.read'],maxBytes:4096}],items:call.mock.calls.some(([m])=>m==='jobs.submit')?[wire()]:[]}
|
|
20
|
+
if(method==='nas.discovery'){if(deny)throw {code:'denied',message:'Cookie=CANARY'};return {files:args?.folder?[{name:'fixture.txt',path:'/home/fixture.txt',isdir:false,additional:{size:3}}]:null,shares:args?.folder?undefined:[{name:'Home',path:'/home',isdir:true}],total:1,offset:0}}
|
|
21
|
+
if(method==='nas.grants.confirm')return {grantId:id('a'),folder:args!.folder,writable:args!.writable}
|
|
22
|
+
if(method==='jobs.prepare')return {intentId:id('c'),jobId:id('b'),summaryDigest:id('d'),expiresAt:Date.now()+30000}
|
|
23
|
+
if(method==='jobs.submit'){if(lost)throw {code:'timeout'};return {jobId:id('b'),attachId:id('e'),revision:1,renewAfterMs:2000,ttlMs:6000}}
|
|
24
|
+
if(method==='jobs.lookup')return wire()
|
|
25
|
+
if(method==='jobs.cancel'){state='canceled';return {}}
|
|
26
|
+
throw {code:'unknown_method'}
|
|
27
|
+
})
|
|
28
|
+
const root=document.createElement('div');document.body.append(root)
|
|
29
|
+
const ctx={locale,call,reopen(fn:()=>void){reopen.push(fn);return ()=>{}}} as Context
|
|
30
|
+
const app=mount(root,ctx)
|
|
31
|
+
return {root,app,call,reopen,done(){state='succeeded'},fence(){state='canceled';reason='fenced'},lost(){lost=true},deny(){deny=true},switchUser(){user='normal2'},close(){app.unmount();root.remove()}}
|
|
32
|
+
}
|
|
33
|
+
// The UI Select is a Reka combobox: drive it by keyboard (the UI README helper), which also proves it is keyboard operable.
|
|
34
|
+
// Attrs such as data-job-action land on the focusable combobox itself.
|
|
35
|
+
async function choose(root:HTMLElement,marker:string,label:string){
|
|
36
|
+
const trigger=root.querySelector<HTMLElement>(`[${marker}][role=combobox]`)!;expect(trigger,marker).toBeTruthy()
|
|
37
|
+
expect(trigger.id,marker).toBeTruthy();expect(root.querySelector(`label[for="${trigger.id}"]`),marker).toBeTruthy()
|
|
38
|
+
trigger.dispatchEvent(new KeyboardEvent('keydown',{key:'ArrowDown',bubbles:true,cancelable:true}));await settle()
|
|
39
|
+
const option=[...document.querySelectorAll<HTMLElement>('[role=option]')].find(o=>o.textContent?.trim()===label)
|
|
40
|
+
if(!option||option.getAttribute('aria-disabled')==='true')throw new Error(`No enabled option: ${label}`)
|
|
41
|
+
option.focus();option.dispatchEvent(new KeyboardEvent('keydown',{key:'Enter',bubbles:true,cancelable:true}));await settle()
|
|
42
|
+
expect(trigger.textContent).toContain(label)
|
|
43
|
+
}
|
|
44
|
+
async function pick(f:ReturnType<typeof fixture>,file=true){
|
|
45
|
+
const j=jobCopy('en'),n=m2Copy('en')
|
|
46
|
+
click(f.root,j.chooseSource);await settle()
|
|
47
|
+
if(file){click(f.root,'Open Home');await settle();const button=f.root.querySelector<HTMLButtonElement>('button[aria-label="Select fixture.txt"]')!;expect(button.textContent?.trim()).toBe('Select fixture.txt');click(f.root,'Select fixture.txt')}
|
|
48
|
+
else click(f.root,'Select Home')
|
|
49
|
+
await settle();click(f.root,n.confirm);await settle()
|
|
50
|
+
}
|
|
51
|
+
test('SDK mount connects file picker, folder consent, worker revision, single submit and completion/cancel/fence',async()=>{
|
|
52
|
+
const f=fixture(),j=jobCopy('en')
|
|
53
|
+
try{
|
|
54
|
+
await settle();expect(f.call.mock.calls.some(([m])=>m==='personal.list')).toBe(false);await pick(f)
|
|
55
|
+
expect(f.call).toHaveBeenCalledWith('nas.grants.confirm',{folder:'/home',writable:false})
|
|
56
|
+
expect(f.root.textContent).toContain('/home/fixture.txt')
|
|
57
|
+
expect(f.root.textContent).not.toContain('NAS folder grants and file operations are not available yet.')
|
|
58
|
+
click(f.root,j.submitInteractive);click(f.root,j.submitInteractive);await settle()
|
|
59
|
+
expect(f.call).toHaveBeenCalledWith('jobs.prepare',{worker:{id:'inspect',revision:7},parent:{id:id('a'),revision:1},input:{steps:[{action:'nas.files.stat',segments:['fixture.txt']}]}})
|
|
60
|
+
expect(f.call.mock.calls.filter(([m])=>m==='jobs.submit')).toHaveLength(1)
|
|
61
|
+
expect(f.root.querySelector('progress')).toBeTruthy()
|
|
62
|
+
click(f.root,j.cancel);await settle();expect(f.root.textContent).toContain(j.canceled)
|
|
63
|
+
f.fence();click(f.root.querySelector<HTMLElement>('.jobs-panel')!,j.refresh);await settle();expect(f.root.textContent).toContain(j.fenceCanceled)
|
|
64
|
+
f.done();click(f.root.querySelector<HTMLElement>('.jobs-panel')!,j.refresh);await settle();expect(f.root.textContent).toContain(j.succeeded)
|
|
65
|
+
}finally{f.close()}
|
|
66
|
+
})
|
|
67
|
+
test('lost initial submit reply locks the form to original lookup; permissions fail without grant or submit',async()=>{
|
|
68
|
+
const f=fixture(),j=jobCopy('en')
|
|
69
|
+
try{
|
|
70
|
+
await settle();await pick(f);f.lost();click(f.root,j.submitInteractive);await settle()
|
|
71
|
+
expect(f.root.textContent).toContain(j.uncertain)
|
|
72
|
+
const button=[...f.root.querySelectorAll('button')].find(b=>b.textContent?.trim()===j.submitInteractive);expect(button?.disabled).toBe(true)
|
|
73
|
+
click(f.root,j.lookup);await settle()
|
|
74
|
+
expect(f.call).toHaveBeenCalledWith('jobs.lookup',{intentId:id('c')})
|
|
75
|
+
expect(f.call.mock.calls.filter(([m])=>m==='jobs.submit')).toHaveLength(1)
|
|
76
|
+
}finally{f.close()}
|
|
77
|
+
const g=fixture()
|
|
78
|
+
try{await settle();g.deny();click(g.root,j.chooseSource);await settle();expect(g.root.textContent).toContain(m2Copy('en').errorDenied);expect(g.root.textContent).not.toContain('CANARY');expect(g.call.mock.calls.some(([m])=>m==='nas.grants.confirm'||m==='jobs.submit')).toBe(false)}finally{g.close()}
|
|
79
|
+
})
|
|
80
|
+
test('folder list/read bounds and identity switch clear pending input; nine job catalogs stay complete',async()=>{
|
|
81
|
+
const f=fixture(),j=jobCopy('en')
|
|
82
|
+
try{
|
|
83
|
+
await settle();await pick(f,false)
|
|
84
|
+
await choose(f.root,'data-job-action',j.listOperation)
|
|
85
|
+
const limit=f.root.querySelector<HTMLInputElement>('input[data-job-bound]')!;limit.value='101';limit.dispatchEvent(new Event('input',{bubbles:true}));await settle()
|
|
86
|
+
click(f.root,j.submitInteractive);await settle();expect(f.call.mock.calls.some(([m])=>m==='jobs.prepare')).toBe(false)
|
|
87
|
+
limit.value='5';limit.dispatchEvent(new Event('input',{bubbles:true}));await settle();click(f.root,j.submitInteractive);await settle()
|
|
88
|
+
expect(f.call).toHaveBeenCalledWith('jobs.prepare',{worker:{id:'inspect',revision:7},parent:{id:id('a'),revision:1},input:{steps:[{action:'nas.files.list',segments:[],limit:5}]}})
|
|
89
|
+
f.switchUser();f.reopen.forEach(fn=>fn());await settle();expect(f.root.querySelector('[data-job-action]')).toBeNull()
|
|
90
|
+
}finally{f.close()}
|
|
91
|
+
for(const copy of Object.values(jobCatalogs)){expect(Object.keys(copy).sort()).toEqual(Object.keys(jobCatalogs.en).sort());for(const key of Object.keys(copy) as Array<keyof typeof copy>){expect(copy[key].trim()).not.toBe('');expect(copy[key].match(/\{\w+\}/g)??[]).toEqual(jobCatalogs.en[key].match(/\{\w+\}/g)??[])}}
|
|
92
|
+
})
|
|
93
|
+
|
|
94
|
+
test('read input is bounded by the live worker and late prepare cannot submit after account switch',async()=>{
|
|
95
|
+
const f=fixture(),j=jobCopy('en')
|
|
96
|
+
try{
|
|
97
|
+
await settle();await pick(f)
|
|
98
|
+
await choose(f.root,'data-job-action',j.readOperation)
|
|
99
|
+
const bound=f.root.querySelector<HTMLInputElement>('input[data-job-bound]')!;expect(bound.max).toBe('4096');expect(f.root.querySelector<HTMLLabelElement>(`label[for="${bound.id}"]`)?.textContent).toContain(j.byteLimit)
|
|
100
|
+
bound.value='24';bound.dispatchEvent(new Event('input',{bubbles:true}));await settle();click(f.root.querySelector<HTMLElement>('.jobs-panel')!,j.refresh);await settle();expect(bound.value).toBe('24')
|
|
101
|
+
bound.value='4097';bound.dispatchEvent(new Event('input',{bubbles:true}));await settle();click(f.root,j.submitInteractive);await settle();expect(f.call.mock.calls.some(([m])=>m==='jobs.prepare')).toBe(false)
|
|
102
|
+
bound.value='3';bound.dispatchEvent(new Event('input',{bubbles:true}));await settle()
|
|
103
|
+
let done!:(value:unknown)=>void
|
|
104
|
+
const original=f.call.getMockImplementation()!
|
|
105
|
+
f.call.mockImplementation(async(method,args)=>method==='jobs.prepare'?new Promise(resolve=>done=resolve):original(method,args))
|
|
106
|
+
click(f.root,j.submitInteractive);await settle();f.switchUser();f.reopen.forEach(fn=>fn());await settle()
|
|
107
|
+
done({intentId:id('c'),jobId:id('b'),summaryDigest:id('d'),expiresAt:Date.now()+30000});await settle()
|
|
108
|
+
expect(f.call.mock.calls.some(([m])=>m==='jobs.submit')).toBe(false)
|
|
109
|
+
}finally{f.close()}
|
|
110
|
+
})
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
<!doctype html><html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Shell local fixture</title><style>html,body{margin:0;height:100%}#root{height:100%}</style></head>
|
|
2
|
+
<body><div id="root"></div><script type="module">
|
|
3
|
+
import {mount} from '../src/main.ts'
|
|
4
|
+
import {createMockNasService} from '../src/nas/mock-provider.ts'
|
|
5
|
+
// Synthetic responses for local browser QA of the DSM shell. No NAS, no credentials.
|
|
6
|
+
const params=new URLSearchParams(location.search),state=params.get('state')||'ready',locale=params.get('locale')||'en'
|
|
7
|
+
mount(document.querySelector('#root'),{
|
|
8
|
+
locale,reopen(){return()=>{}},
|
|
9
|
+
async call(method){
|
|
10
|
+
if(method==='app.status')return {package:'T0133 Sidebar Demo',version:'0.1.0-0001',user:'admin',admin:true}
|
|
11
|
+
if(method==='integration.status')return {state,reason:state==='ready'?'fixture':'not-requested',packageRunning:'running',scheduler:'ready'}
|
|
12
|
+
if(method==='identity.status')return {mode:'better-auth'}
|
|
13
|
+
if(method==='personal.list')return {items:[{id:'demo-private',name:'My sample note'}]}
|
|
14
|
+
if(method==='personal.read')return {id:'demo-private',text:'Remember to check the DSM window layout.'}
|
|
15
|
+
throw {code:'unknown_method'}
|
|
16
|
+
},
|
|
17
|
+
root:{state:async()=> 'missing',authorize:async()=> 'ready',run:async()=>{},remove:async()=> 'missing'},
|
|
18
|
+
},{nasProvider:createMockNasService().connect('admin')})
|
|
19
|
+
</script></body></html>
|
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {expect,test,vi} from 'vitest'
|
|
3
|
+
import {createApp,h,nextTick} from 'vue'
|
|
4
|
+
import SDKApp from '../src/SDKApp.vue'
|
|
5
|
+
import {JobsProviderKey,type JobsProvider} from '../src/jobs/provider'
|
|
6
|
+
import {jobCopy} from '../src/jobs/i18n'
|
|
7
|
+
import {createMockNasService} from '../src/nas/mock-provider'
|
|
8
|
+
import {NasProviderKey} from '../src/nas/provider'
|
|
9
|
+
import {shellCopy} from '../src/shell/i18n'
|
|
10
|
+
import type {Context} from '../src/context'
|
|
11
|
+
|
|
12
|
+
async function settle(){for(let i=0;i<16;i++){await Promise.resolve();await nextTick()}await new Promise(resolve=>setTimeout(resolve,25))}
|
|
13
|
+
function navLabels(root:HTMLElement){return [...root.querySelectorAll<HTMLElement>('[data-nav-item]')].map(node=>node.textContent?.trim())}
|
|
14
|
+
function heading(root:HTMLElement){return root.querySelector('h1')?.textContent?.trim()}
|
|
15
|
+
function pageText(root:HTMLElement,page:string){return root.querySelector(`[data-page="${page}"]`)?.textContent??''}
|
|
16
|
+
function clickNav(root:HTMLElement,label:string){const node=[...root.querySelectorAll<HTMLElement>('[data-nav-item]')].find(node=>node.textContent?.trim()===label);expect(node,label).toBeTruthy();node!.click()}
|
|
17
|
+
|
|
18
|
+
// A connected provider advertises interactive workers; the mock used by the
|
|
19
|
+
// standalone preview does not. This stub keeps both modes on one provider so
|
|
20
|
+
// the shell test can exercise both pages.
|
|
21
|
+
function provider():JobsProvider{
|
|
22
|
+
return {
|
|
23
|
+
mode:'mock',
|
|
24
|
+
readiness:{interactive:'ready',background:'ready'},
|
|
25
|
+
interactive:{workers:[],prepareInteractive:async()=>{throw {code:'unavailable'}},submitInteractive:async()=>{throw {code:'unavailable'}}},
|
|
26
|
+
list:async()=>({revision:1,items:[]}),
|
|
27
|
+
prepare:async()=>{throw {code:'unavailable'}},
|
|
28
|
+
confirm:async()=>{},
|
|
29
|
+
lookup:async()=>undefined,
|
|
30
|
+
cancel:async()=>{},
|
|
31
|
+
revoke:async()=>({revoked:true,drained:true}),
|
|
32
|
+
disconnect(){},
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
function fixture(admin:boolean,locale='en'){
|
|
36
|
+
const call=vi.fn(async(method:string)=>{
|
|
37
|
+
if(method==='app.status') return {package:'SdkShell',version:'0.1.0-0001',user:admin?'admin':'normal1',admin}
|
|
38
|
+
if(method==='integration.status') return {state:'ready',reason:'fixture',packageRunning:'unknown',scheduler:'not-probed'}
|
|
39
|
+
if(method==='identity.status') return {mode:'none'}
|
|
40
|
+
throw {code:'unknown_method'}
|
|
41
|
+
})
|
|
42
|
+
const ctx={locale,call,reopen:()=>()=>{}} as unknown as Context
|
|
43
|
+
const root=document.createElement('div');document.body.append(root)
|
|
44
|
+
const app=createApp({render:()=>h(SDKApp,{ctx})})
|
|
45
|
+
app.config.idPrefix=`sdk-shell-${Math.random()}`
|
|
46
|
+
app.provide(JobsProviderKey,provider())
|
|
47
|
+
app.provide(NasProviderKey,createMockNasService().connect(admin?'admin':'normal1'))
|
|
48
|
+
app.mount(root)
|
|
49
|
+
return {root,close(){app.unmount();root.remove()}}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
test('interactive and background work get separate pages and the admin page is administrator-only',async()=>{
|
|
53
|
+
const admin=fixture(true),other=fixture(false)
|
|
54
|
+
try{
|
|
55
|
+
await settle()
|
|
56
|
+
expect(navLabels(admin.root)).toEqual(['Status','NAS folder access','Interactive jobs','Background jobs','Job management'])
|
|
57
|
+
expect(navLabels(other.root)).toEqual(['Status','NAS folder access','Interactive jobs','Background jobs'])
|
|
58
|
+
expect(heading(admin.root)).toBe(shellCopy('en').status)
|
|
59
|
+
|
|
60
|
+
clickNav(admin.root,shellCopy('en').interactive);await settle()
|
|
61
|
+
expect(heading(admin.root)).toBe('Interactive jobs')
|
|
62
|
+
expect(pageText(admin.root,'interactive')).toContain(jobCopy('en').interactiveHelp)
|
|
63
|
+
expect(pageText(admin.root,'interactive')).not.toContain(jobCopy('en').backgroundHelp)
|
|
64
|
+
|
|
65
|
+
clickNav(admin.root,shellCopy('en').background);await settle()
|
|
66
|
+
expect(heading(admin.root)).toBe('Background jobs')
|
|
67
|
+
expect(pageText(admin.root,'background')).toContain(jobCopy('en').backgroundHelp)
|
|
68
|
+
expect(pageText(admin.root,'background')).not.toContain(jobCopy('en').interactiveHelp)
|
|
69
|
+
|
|
70
|
+
clickNav(other.root,shellCopy('en').interactive);await settle()
|
|
71
|
+
expect(other.root.textContent).not.toContain(jobCopy('en').adminTitle)
|
|
72
|
+
}finally{admin.close();other.close()}
|
|
73
|
+
})
|
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {expect,test,vi} from 'vitest'
|
|
3
|
+
import {nextTick} from 'vue'
|
|
4
|
+
import {mount} from '../src/main'
|
|
5
|
+
import {createMockNasService} from '../src/nas/mock-provider'
|
|
6
|
+
import {shellCopy} from '../src/shell/i18n'
|
|
7
|
+
import type {Context,IntegrationState} from '../src/context'
|
|
8
|
+
|
|
9
|
+
async function settle(){for(let i=0;i<16;i++){await Promise.resolve();await nextTick()}await new Promise(resolve=>setTimeout(resolve,25))}
|
|
10
|
+
function nav(root:HTMLElement){return [...root.querySelectorAll<HTMLElement>('[data-nav-item]')]}
|
|
11
|
+
function navLabels(root:HTMLElement){return nav(root).map(node=>node.textContent?.trim())}
|
|
12
|
+
function clickNav(root:HTMLElement,label:string){const node=nav(root).find(node=>node.textContent?.trim()===label);expect(node,label).toBeTruthy();node!.click()}
|
|
13
|
+
function heading(root:HTMLElement){return root.querySelector('h1')?.textContent?.trim()}
|
|
14
|
+
function pageShown(root:HTMLElement,page:string){return root.querySelector<HTMLElement>(`[data-page="${page}"]`)?.style.display !== 'none'}
|
|
15
|
+
// A --background project adds one more page; the same test runs in both builds.
|
|
16
|
+
function withBackground(root:HTMLElement,labels:string[]){return labels.flatMap(label=>label==='@@background@@'?(root.querySelector('[data-background-panel]')?[shellCopy('en').background]:[]):[label])}
|
|
17
|
+
|
|
18
|
+
function fixture(options:{state?:IntegrationState;admin?:boolean;identity?:boolean;locale?:string}={}){
|
|
19
|
+
const flags={state:options.state??'ready' as IntegrationState,identity:options.identity??false,admin:options.admin??true,locale:options.locale??'en'}
|
|
20
|
+
const reopen:Array<()=>void>=[]
|
|
21
|
+
const call=vi.fn(async(method:string)=>{
|
|
22
|
+
if(method==='app.status') return {package:'ShellDemo',version:'0.1.0-0001',user:flags.admin?'admin':'normal1',admin:flags.admin}
|
|
23
|
+
if(method==='integration.status') return {state:flags.state,reason:flags.state==='ready'?'fixture':'not-requested',packageRunning:'unknown',scheduler:'not-probed'}
|
|
24
|
+
if(method==='identity.status') return {mode:flags.identity?'better-auth':'none'}
|
|
25
|
+
if(method==='personal.list') return {items:[{id:'ShellDemo-private',name:'My sample note'}]}
|
|
26
|
+
if(method==='personal.read') return {id:'ShellDemo-private',text:'Private ShellDemo'}
|
|
27
|
+
throw {code:'unknown_method'}
|
|
28
|
+
})
|
|
29
|
+
const ctx={locale:flags.locale,reopen:(fn:()=>void)=>reopen.push(fn),call,root:{state:vi.fn(async()=> 'missing'),authorize:vi.fn(async()=> 'ready'),run:vi.fn(async()=>{}),remove:vi.fn(async()=> 'missing')}} as Context
|
|
30
|
+
const root=document.createElement('div');document.body.append(root)
|
|
31
|
+
const app=mount(root,ctx,{nasProvider:createMockNasService().connect(flags.admin?'admin':'normal1')})
|
|
32
|
+
const refresh=async()=>{reopen.forEach(fn=>fn());await settle()}
|
|
33
|
+
return {root,app,flags,refresh,cleanup(){app.unmount();root.remove()}}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
test('the sidebar lists the enabled pages and the first one is shown by default',async()=>{
|
|
37
|
+
const f=fixture({identity:true})
|
|
38
|
+
try{
|
|
39
|
+
await settle()
|
|
40
|
+
expect(navLabels(f.root)).toEqual(withBackground(f.root,['Status','My sample data','NAS folder access','@@background@@','Optional app identity']))
|
|
41
|
+
expect(heading(f.root)).toBe('Status')
|
|
42
|
+
expect(nav(f.root).find(node=>node.textContent?.trim()==='Status')?.getAttribute('aria-current')).toBe('page')
|
|
43
|
+
expect(pageShown(f.root,'status')).toBe(true)
|
|
44
|
+
}finally{f.cleanup()}
|
|
45
|
+
})
|
|
46
|
+
|
|
47
|
+
test('choosing a sidebar entry swaps the page and its header',async()=>{
|
|
48
|
+
const f=fixture({identity:true})
|
|
49
|
+
try{
|
|
50
|
+
await settle()
|
|
51
|
+
clickNav(f.root,'NAS folder access');await settle()
|
|
52
|
+
expect(heading(f.root)).toBe('NAS folder access')
|
|
53
|
+
expect(nav(f.root).find(node=>node.textContent?.trim()==='NAS folder access')?.getAttribute('aria-current')).toBe('page')
|
|
54
|
+
expect(nav(f.root).find(node=>node.textContent?.trim()==='Status')?.getAttribute('aria-current')).toBeNull()
|
|
55
|
+
expect(pageShown(f.root,'status')).toBe(false)
|
|
56
|
+
expect(pageShown(f.root,'nas')).toBe(true)
|
|
57
|
+
clickNav(f.root,'My sample data');await settle()
|
|
58
|
+
expect(heading(f.root)).toBe('My sample data')
|
|
59
|
+
}finally{f.cleanup()}
|
|
60
|
+
})
|
|
61
|
+
|
|
62
|
+
test('pages only appear when the manifest enables their feature',async()=>{
|
|
63
|
+
const pending=fixture({state:'pending',identity:false})
|
|
64
|
+
const ready=fixture({state:'ready',identity:false})
|
|
65
|
+
try{
|
|
66
|
+
await settle()
|
|
67
|
+
// Sample data needs a ready integration; identity needs the better-auth build.
|
|
68
|
+
expect(navLabels(pending.root)).toEqual(withBackground(pending.root,['Status','NAS folder access','@@background@@']))
|
|
69
|
+
expect(navLabels(ready.root)).toEqual(withBackground(ready.root,['Status','My sample data','NAS folder access','@@background@@']))
|
|
70
|
+
expect(pending.root.textContent).not.toContain('My sample data')
|
|
71
|
+
}finally{pending.cleanup();ready.cleanup()}
|
|
72
|
+
})
|
|
73
|
+
|
|
74
|
+
test('navigation is keyboard operable through native buttons and the catalog follows DSM locales',async()=>{
|
|
75
|
+
const f=fixture({identity:true,locale:'cht'})
|
|
76
|
+
try{
|
|
77
|
+
await settle()
|
|
78
|
+
for(const node of nav(f.root)){
|
|
79
|
+
expect(node.tagName).toBe('BUTTON')
|
|
80
|
+
expect(node.getAttribute('type')).toBe('button')
|
|
81
|
+
expect(node.getAttribute('tabindex')).toBeNull()
|
|
82
|
+
expect(node.getAttribute('aria-label')||node.textContent?.trim()).toBeTruthy()
|
|
83
|
+
}
|
|
84
|
+
clickNav(f.root,shellCopy('zh-TW').nas);await settle()
|
|
85
|
+
expect(heading(f.root)).toBe(shellCopy('zh-TW').nas)
|
|
86
|
+
}finally{f.cleanup()}
|
|
87
|
+
})
|
|
88
|
+
|
|
89
|
+
test('a page that disappears from the manifest falls back to the first page',async()=>{
|
|
90
|
+
const f=fixture()
|
|
91
|
+
try{
|
|
92
|
+
await settle()
|
|
93
|
+
clickNav(f.root,'My sample data');await settle()
|
|
94
|
+
expect(heading(f.root)).toBe('My sample data')
|
|
95
|
+
// The integration stops being ready while its sample page is selected.
|
|
96
|
+
f.flags.state='pending'
|
|
97
|
+
await f.refresh()
|
|
98
|
+
expect(navLabels(f.root)).toEqual(withBackground(f.root,['Status','NAS folder access','@@background@@']))
|
|
99
|
+
expect(heading(f.root)).toBe('Status')
|
|
100
|
+
expect(pageShown(f.root,'status')).toBe(true)
|
|
101
|
+
expect(pageShown(f.root,'sample')).toBe(false)
|
|
102
|
+
}finally{f.cleanup()}
|
|
103
|
+
})
|
|
104
|
+
|
|
105
|
+
test('the page header and the page content share one PageContent boundary',async()=>{
|
|
106
|
+
const f=fixture({identity:true})
|
|
107
|
+
try{
|
|
108
|
+
await settle()
|
|
109
|
+
const body=f.root.querySelector('[data-page-content-body]')
|
|
110
|
+
expect(body).toBeTruthy()
|
|
111
|
+
expect(body!.contains(f.root.querySelector('h1'))).toBe(true)
|
|
112
|
+
expect(body!.contains(f.root.querySelector('[data-page="status"]'))).toBe(true)
|
|
113
|
+
}finally{f.cleanup()}
|
|
114
|
+
})
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
// @vitest-environment jsdom
|
|
2
|
+
import {expect,test} from 'vitest'
|
|
3
|
+
import {activePage,shellPages,type ShellFeatureFlags} from '../src/shell/pages'
|
|
4
|
+
import {shellCatalogs,shellCopy} from '../src/shell/i18n'
|
|
5
|
+
|
|
6
|
+
test('status is always first and secondary pages follow their feature flag',()=>{
|
|
7
|
+
expect(shellPages({})).toEqual(['status'])
|
|
8
|
+
expect(shellPages({sample:true,nas:true,interactive:true,background:true,admin:true,identity:true}))
|
|
9
|
+
.toEqual(['status','sample','nas','interactive','background','admin','identity'])
|
|
10
|
+
// Unknown or disabled features never leak into the navigation.
|
|
11
|
+
expect(shellPages({sample:false,nas:false,admin:false})).toEqual(['status'])
|
|
12
|
+
})
|
|
13
|
+
|
|
14
|
+
test('a selected page that is no longer available falls back to the first page',()=>{
|
|
15
|
+
const pages=shellPages({sample:true,admin:true})
|
|
16
|
+
expect(activePage('admin',pages)).toBe('admin')
|
|
17
|
+
expect(activePage('nas',pages)).toBe('status')
|
|
18
|
+
expect(activePage(undefined,pages)).toBe('status')
|
|
19
|
+
expect(activePage('status',shellPages({folders:true}))).toBe('status')
|
|
20
|
+
expect(shellPages({folders:true})).toEqual(['status','folders'])
|
|
21
|
+
})
|
|
22
|
+
|
|
23
|
+
test('feature flags accept plain booleans without a build-time manifest read',()=>{
|
|
24
|
+
const flags:ShellFeatureFlags={sample:true,interactive:false}
|
|
25
|
+
expect(shellPages(flags)).toEqual(['status','sample'])
|
|
26
|
+
})
|
|
27
|
+
|
|
28
|
+
test('nine navigation catalogs stay complete and resolve DSM locale aliases',()=>{
|
|
29
|
+
expect(Object.keys(shellCatalogs)).toEqual(['en','zh-TW','zh-CN','ja','ko','de','fr','es','it'])
|
|
30
|
+
for(const catalog of Object.values(shellCatalogs)){
|
|
31
|
+
expect(Object.keys(catalog).sort()).toEqual(Object.keys(shellCatalogs.en).sort())
|
|
32
|
+
expect(Object.values(catalog).every(value=>typeof value==='string'&&value.trim().length>0)).toBe(true)
|
|
33
|
+
}
|
|
34
|
+
expect(shellCopy('cht')).toEqual(shellCatalogs['zh-TW'])
|
|
35
|
+
expect(shellCopy('zh_CN')).toEqual(shellCatalogs['zh-CN'])
|
|
36
|
+
expect(shellCopy('unknown')).toEqual(shellCatalogs.en)
|
|
37
|
+
expect(Object.values(shellCatalogs).every(catalog=>typeof catalog.background==='string'&&catalog.background.trim().length>0)).toBe(true)
|
|
38
|
+
})
|
|
39
|
+
|
|
40
|
+
test('navigation labels are unique inside every catalog',()=>{
|
|
41
|
+
for(const catalog of Object.values(shellCatalogs)){
|
|
42
|
+
const labels=Object.values(catalog).map(value=>value.trim().toLowerCase())
|
|
43
|
+
expect(new Set(labels).size).toBe(labels.length)
|
|
44
|
+
}
|
|
45
|
+
})
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { defineConfig } from 'vite'
|
|
1
|
+
import { defineConfig, loadEnv } from 'vite'
|
|
2
2
|
import vue from '@vitejs/plugin-vue'
|
|
3
3
|
// @ts-expect-error The shared notice tool is a plain Node module.
|
|
4
4
|
import { writeThirdPartyNotices } from './third-party-notices.mjs'
|
|
@@ -6,5 +6,5 @@ export default defineConfig(({command}) => ({
|
|
|
6
6
|
plugins: [vue(), {name: 'dependency-notices', closeBundle() {writeThirdPartyNotices('.', 'dist/LICENSES', {ui: true})}}],
|
|
7
7
|
// Library mode must replace Vue dependency env guards for the DSM browser.
|
|
8
8
|
define: command === 'build' ? {'process.env.NODE_ENV': JSON.stringify('production')} : {},
|
|
9
|
-
build: {lib: {entry: 'src/main.ts', formats: ['iife'], name: 'DsmVueApp', fileName: () => 'app.js'}, cssCodeSplit: false, rollupOptions: {output: {assetFileNames: 'app.[ext]'}}},
|
|
9
|
+
build: {lib: {entry: loadEnv('production', '.', '').SYNOPKGLAND_M2_SIMPLE === '1' ? 'src/m2-main.ts' : loadEnv('production', '.', '').SYNOPKGLAND_SDK_WORKERS === '1' ? 'src/sdk-main.ts' : 'src/main.ts', formats: ['iife'], name: 'DsmVueApp', fileName: () => 'app.js'}, cssCodeSplit: false, rollupOptions: {output: {assetFileNames: 'app.[ext]'}}},
|
|
10
10
|
}))
|