hap-cli 0.8.28__tar.gz → 0.8.29__tar.gz
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.
- {hap_cli-0.8.28 → hap_cli-0.8.29}/PKG-INFO +1 -1
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/__init__.py +1 -1
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/worksheet_cmd.py +122 -3
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/worksheet.py +229 -6
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/locale/messages.json +24 -4
- hap_cli-0.8.29/hap_cli/tests/test_worksheet_switch.py +174 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli.egg-info/PKG-INFO +1 -1
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli.egg-info/SOURCES.txt +1 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/MANIFEST.in +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/README.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/app_live_tests/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/app_live_tests/__main__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/app_live_tests/config.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/app_live_tests/harness.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/app_live_tests/smoke.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/app_live_tests/state.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/README.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/README_CN.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/__main__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/app_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/app_creator_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/app_editor_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/attachment_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/auth_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/calendar_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/chart_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/chat_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/chatbot_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/config_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/contact_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/department_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/group_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/guide_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/icon_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/instance_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/node_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/optionset_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/page_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/post_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/record_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/region_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/role_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/update_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/upload_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/v3_registry.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/commands/workflow_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/context.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/action_spec_adapter.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/_hapmeta/worksheet_templates.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/charts.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/cleanup.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/compiler.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/config.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/design/design.schema.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/errors.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/examples/minimal.design.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/executor.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/fields.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/hap.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/merge.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/recording/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/recording/console.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/recording/jsonl.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/recording/mirror.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/recording/report.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/schema.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/INSTRUCTIONS.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/executor.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/resources/attachments.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/resources/sample.docx +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/resources/sample.pdf +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/resources/sample_images.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/seed/template.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/selftest.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/smoke.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/steps.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/store.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/validate.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/app_creator/workflow_dsl.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/attachment.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/auth.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/calendar_mod.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/chart.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/chat.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/chatbot.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/cli_access.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/contact.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/control_type_codes.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/department.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/_hapmeta/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/_hapmeta/control_type_codes.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/apply.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/componentlower.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/editspec/component.schema.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/editspec/custom-action.schema.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/editspec/envelope.schema.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/editspec/field.schema.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/editspec.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/errors.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/fieldlower.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/jsonschema_mini.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/models.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/ops.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/planner.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/editor/reader.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/field_normalizer.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/field_spec_adapter.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/filter_translator.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/flow_node.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/global_meta.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/group.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/guide.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/icon_index.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/instance.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/logger.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/optionset.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/page.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/post.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/record.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/response_crypto.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/role.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/role_perm_builder.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/session.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/token_crypto.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/update.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/upload.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/v3/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/v3/dispatcher.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/v3/render.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/v3/schema.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/view_spec_adapter.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/workflow.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/workflow_node_dsl.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/core/worksheet_templates.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/en/approval.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/en/chart.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/en/messages.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/en/overview.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/en/post.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/en/record.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/en/setup.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/en/skills.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/index.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/zh-Hans/approval.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/zh-Hans/chart.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/zh-Hans/messages.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/zh-Hans/overview.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/zh-Hans/post.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/zh-Hans/record.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/zh-Hans/setup.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/guide/zh-Hans/skills.md +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/hap_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/i18n.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/locale/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/locale/messages.schema.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/conftest.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_app_creator.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_app_editor.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_app_export_usage.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_approval_submit_fill_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_attachment_share.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_auth_prerelease.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_bug_036430.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_bug_036750.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_chart.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_chat_attachment_send.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_chat_recall_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_cli_access.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_config_cmd.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_content_language.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_control_lookup_guard.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_core.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_destructive_confirm_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_full_e2e.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_global_meta.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_guide.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_i18n.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_instance_history_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_approval.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_approval_actions.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_calendar.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_destructive.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_misc.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_post.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_social.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_v3.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_workflow.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_integration_worksheet_extra.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_member_relation_array_wrap.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_multi_profile.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_no_workflow_unsupported.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_org_approval_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_org_id_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_org_id_docs.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_parameter_conventions.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_parameter_mapping_registry.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_post_scope_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_record_batch_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_record_field_alias.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_record_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_record_write_fixes.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_select_add_option.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_select_rejects_relation_value.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_semantic_types_registry.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_update.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_update_mode.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_upload_host_resolution.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_v3_api_schema_loader.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_v3_dispatcher.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_v3_registry_coverage.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_v3_session.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_v3_yaml_translation.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_workflow_envelope_errors.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_workflow_list_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_workflow_read_write_keys.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_workflow_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_worksheet_create_section.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_worksheet_crud_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/tests/test_worksheet_v3_semantic_cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/utils/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/utils/formatting.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/utils/options.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli/utils/parameter_mapping.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli.egg-info/dependency_links.txt +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli.egg-info/entry_points.txt +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli.egg-info/requires.txt +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/hap_cli.egg-info/top_level.txt +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/__main__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/_wftest.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/charts.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/cleanup.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/compiler.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/config.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/errors.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/executor.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/fields.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/hap.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/recording/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/recording/console.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/recording/jsonl.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/recording/mirror.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/recording/report.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/schema.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/seed/__init__.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/seed/cli.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/seed/executor.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/seed/template.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/selftest.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/smoke.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/step.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/steps.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/store.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/live_tests/workflow_dsl.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/setup.cfg +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/setup.py +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/INDEX.json +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/add_member_to_role.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/batch_delete_records.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/create_optionset.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/create_role.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/delete_optionset.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/delete_record.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/delete_role.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/delete_worksheet.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_app_info.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_app_knowledge_list.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_app_worksheets_list.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_optionset_list.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_record_details.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_record_discussions.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_record_list.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_record_logs.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_record_pivot_data.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_record_relations.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_record_share_link.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_regions.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_role_details.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/get_role_list.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/knowledge_search.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/leave_all_roles.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/remove_member_from_role.yaml +0 -0
- {hap_cli-0.8.28 → hap_cli-0.8.29}/sources/v3-api-schema/update_optionset.yaml +0 -0
|
@@ -919,24 +919,143 @@ def worksheet_delete_button(ctx, worksheet_id, btn_id, app_id, view_id, yes):
|
|
|
919
919
|
|
|
920
920
|
@worksheet.command("switches")
|
|
921
921
|
@click.argument("worksheet_id")
|
|
922
|
+
@click.option("--raw", is_flag=True,
|
|
923
|
+
help="Return the server's switch list unannotated.")
|
|
922
924
|
@pass_context
|
|
923
|
-
def worksheet_switches(ctx, worksheet_id):
|
|
924
|
-
"""Get feature switches for a worksheet.
|
|
925
|
+
def worksheet_switches(ctx, worksheet_id, raw):
|
|
926
|
+
"""Get feature switches for a worksheet.
|
|
927
|
+
|
|
928
|
+
Each switch is listed with the name and group it has on the feature
|
|
929
|
+
switch page, because several numbers share a name and only the group
|
|
930
|
+
tells them apart.
|
|
931
|
+
"""
|
|
925
932
|
try:
|
|
926
933
|
session = ctx.get_session()
|
|
927
934
|
data = ws_mod.get_switches(session, worksheet_id)
|
|
935
|
+
if not raw:
|
|
936
|
+
data = ws_mod.annotate_switches(data)
|
|
928
937
|
ctx.output(data, lambda d: output_json(d))
|
|
929
938
|
except Exception as e:
|
|
930
939
|
ctx.handle_error(e)
|
|
931
940
|
|
|
932
941
|
|
|
942
|
+
@worksheet.command("switch-types")
|
|
943
|
+
@pass_context
|
|
944
|
+
def worksheet_switch_types(ctx):
|
|
945
|
+
"""Show the feature switch numbers with their names and groups."""
|
|
946
|
+
rows = [ws_mod.describe_switch(t) for t in sorted(ws_mod.SWITCH_TYPES)]
|
|
947
|
+
ctx.output(
|
|
948
|
+
rows,
|
|
949
|
+
lambda d: output_table(
|
|
950
|
+
d, ["type", "group", "name", "path"],
|
|
951
|
+
["Type", "Group", "Name", "Path"],
|
|
952
|
+
),
|
|
953
|
+
)
|
|
954
|
+
|
|
955
|
+
|
|
956
|
+
def _parse_switch_changes(pairs):
|
|
957
|
+
changes = {}
|
|
958
|
+
for raw in pairs:
|
|
959
|
+
if "=" not in raw:
|
|
960
|
+
raise click.UsageError(
|
|
961
|
+
f"Invalid --set value {raw!r}. Use --set TYPE=true|false.")
|
|
962
|
+
key, value = raw.split("=", 1)
|
|
963
|
+
try:
|
|
964
|
+
stype = int(key.strip())
|
|
965
|
+
except ValueError:
|
|
966
|
+
raise click.UsageError(
|
|
967
|
+
f"Invalid switch number {key!r}. Run `hap worksheet "
|
|
968
|
+
"switch-types` for the list.")
|
|
969
|
+
val = value.strip().lower()
|
|
970
|
+
if val not in ("true", "false", "1", "0", "on", "off"):
|
|
971
|
+
raise click.UsageError(
|
|
972
|
+
f"Invalid state {value!r} for switch {stype}. Use true or false.")
|
|
973
|
+
changes[stype] = val in ("true", "1", "on")
|
|
974
|
+
return changes
|
|
975
|
+
|
|
976
|
+
|
|
977
|
+
@worksheet.command("batch-edit-switch")
|
|
978
|
+
@click.argument("worksheet_ids", nargs=-1, required=True)
|
|
979
|
+
@click.option("--set", "pairs", multiple=True, required=True,
|
|
980
|
+
help="Switch to change as TYPE=true|false (repeatable). "
|
|
981
|
+
"Numbers come from `hap worksheet switch-types`.")
|
|
982
|
+
@click.option("--dry-run", is_flag=True,
|
|
983
|
+
help="Show what would change on each worksheet, without saving.")
|
|
984
|
+
@pass_context
|
|
985
|
+
def worksheet_batch_edit_switch(ctx, worksheet_ids, pairs, dry_run):
|
|
986
|
+
"""Change several feature switches on one or more worksheets.
|
|
987
|
+
|
|
988
|
+
The switches you do not name keep their current values. Turning the
|
|
989
|
+
批量操作 or 统计 parent on or off carries its children with it. Each
|
|
990
|
+
worksheet is read back after saving, and any switch that did not take
|
|
991
|
+
is reported rather than counted as success.
|
|
992
|
+
"""
|
|
993
|
+
try:
|
|
994
|
+
changes = _parse_switch_changes(pairs)
|
|
995
|
+
session = ctx.get_session()
|
|
996
|
+
if dry_run:
|
|
997
|
+
preview = [
|
|
998
|
+
{**ws_mod.describe_switch(t), "state": v}
|
|
999
|
+
for t, v in changes.items()
|
|
1000
|
+
]
|
|
1001
|
+
ctx.output(
|
|
1002
|
+
{"worksheetIds": list(worksheet_ids), "changes": preview,
|
|
1003
|
+
"saved": False},
|
|
1004
|
+
lambda d: click.echo(
|
|
1005
|
+
f"Would set {len(preview)} switch(es) on "
|
|
1006
|
+
f"{len(worksheet_ids)} worksheet(s): "
|
|
1007
|
+
+ ", ".join(f"{c['path'] or c['type']}={c['state']}"
|
|
1008
|
+
for c in preview)),
|
|
1009
|
+
)
|
|
1010
|
+
return
|
|
1011
|
+
results, failed_sheets = [], []
|
|
1012
|
+
for wid in worksheet_ids:
|
|
1013
|
+
try:
|
|
1014
|
+
res = ws_mod.batch_edit_switch(session, wid, changes)
|
|
1015
|
+
except Exception as exc: # keep going across worksheets
|
|
1016
|
+
results.append({"worksheetId": wid, "error": str(exc)})
|
|
1017
|
+
failed_sheets.append(wid)
|
|
1018
|
+
continue
|
|
1019
|
+
results.append(res)
|
|
1020
|
+
if res.get("failed"):
|
|
1021
|
+
failed_sheets.append(wid)
|
|
1022
|
+
payload = {"results": results,
|
|
1023
|
+
"worksheetsChanged": len(worksheet_ids) - len(failed_sheets),
|
|
1024
|
+
"worksheetsFailed": failed_sheets}
|
|
1025
|
+
|
|
1026
|
+
def _render(d):
|
|
1027
|
+
click.echo(f"Changed {d['worksheetsChanged']} of "
|
|
1028
|
+
f"{len(worksheet_ids)} worksheet(s).")
|
|
1029
|
+
for r in d["results"]:
|
|
1030
|
+
if r.get("error"):
|
|
1031
|
+
click.echo(f" {r['worksheetId']}: {r['error']}")
|
|
1032
|
+
elif r.get("failed"):
|
|
1033
|
+
for t, info in r["failed"].items():
|
|
1034
|
+
click.echo(f" {r['worksheetId']}: switch {t} is "
|
|
1035
|
+
f"{info['actual']!r}, wanted "
|
|
1036
|
+
f"{info['expected']!r}")
|
|
1037
|
+
ctx.output(payload, _render)
|
|
1038
|
+
if failed_sheets:
|
|
1039
|
+
raise click.ClickException(
|
|
1040
|
+
f"{len(failed_sheets)} worksheet(s) did not end up in the "
|
|
1041
|
+
"requested state: " + ", ".join(failed_sheets))
|
|
1042
|
+
except Exception as e:
|
|
1043
|
+
ctx.handle_error(e)
|
|
1044
|
+
|
|
1045
|
+
|
|
933
1046
|
@worksheet.command("edit-switch")
|
|
934
1047
|
@click.argument("worksheet_id")
|
|
935
1048
|
@click.argument("switch_id", type=int)
|
|
936
1049
|
@click.argument("value", type=bool)
|
|
937
1050
|
@pass_context
|
|
938
1051
|
def worksheet_edit_switch(ctx, worksheet_id, switch_id, value):
|
|
939
|
-
"""
|
|
1052
|
+
"""Turn one feature switch on or off.
|
|
1053
|
+
|
|
1054
|
+
SWITCH_ID is a switch number from `hap worksheet switch-types`. The
|
|
1055
|
+
new state is read back, so a change that did not take is reported as
|
|
1056
|
+
an error instead of success. To change several switches, or the same
|
|
1057
|
+
ones across many worksheets, use `worksheet batch-edit-switch`.
|
|
1058
|
+
"""
|
|
940
1059
|
try:
|
|
941
1060
|
session = ctx.get_session()
|
|
942
1061
|
data = ws_mod.edit_switch(session, worksheet_id, switch_id, value)
|
|
@@ -4,7 +4,7 @@ import json
|
|
|
4
4
|
import logging
|
|
5
5
|
from typing import Any, Optional
|
|
6
6
|
|
|
7
|
-
from hap_cli.core.session import Session
|
|
7
|
+
from hap_cli.core.session import Session, APIError
|
|
8
8
|
from hap_cli.core import worksheet_templates as wt
|
|
9
9
|
|
|
10
10
|
logger = logging.getLogger("hap_cli.worksheet")
|
|
@@ -1681,14 +1681,237 @@ def get_switches(
|
|
|
1681
1681
|
)
|
|
1682
1682
|
|
|
1683
1683
|
|
|
1684
|
+
# ── Feature switch catalog ───────────────────────────────────────────────
|
|
1685
|
+
#
|
|
1686
|
+
# GetSwitch answers bare numbers, which say nothing about what they
|
|
1687
|
+
# control, and several numbers share a label: 26 and 36 are both "复制",
|
|
1688
|
+
# 28 and 39 are both "删除", 23 and 32 are both "系统默认打印". Only the
|
|
1689
|
+
# group tells them apart, so the group travels with the name.
|
|
1690
|
+
#
|
|
1691
|
+
# Names, grouping and parent/child structure come from pd-openweb
|
|
1692
|
+
# ``src/pages/FormSet/containers/FunctionalSwitch/config.js``
|
|
1693
|
+
# (``listConfigStr``, ``worksheetSwitch`` / ``viewSwitch`` /
|
|
1694
|
+
# ``recordSwitch`` / ``approveSwitch``, ``batch``, ``statistics``).
|
|
1695
|
+
|
|
1696
|
+
SWITCH_PARENT_BATCH = 25 # 视图 > 批量操作
|
|
1697
|
+
SWITCH_PARENT_STATISTICS = 500 # 工作表 > 统计
|
|
1698
|
+
|
|
1699
|
+
_SWITCH_BATCH_CHILDREN = (24, 26, 23, 27, 28, 29)
|
|
1700
|
+
_SWITCH_STATISTICS_CHILDREN = (50, 51)
|
|
1701
|
+
|
|
1702
|
+
SWITCH_TYPES: dict[int, dict[str, Any]] = {
|
|
1703
|
+
# 工作表
|
|
1704
|
+
10: {"name": "显示创建按钮", "group": "工作表"},
|
|
1705
|
+
11: {"name": "讨论", "group": "工作表"},
|
|
1706
|
+
12: {"name": "日志", "group": "工作表"},
|
|
1707
|
+
13: {"name": "导入", "group": "工作表"},
|
|
1708
|
+
14: {"name": "回收站", "group": "工作表"},
|
|
1709
|
+
500: {"name": "统计", "group": "工作表"},
|
|
1710
|
+
50: {"name": "公共", "group": "工作表", "parent": SWITCH_PARENT_STATISTICS},
|
|
1711
|
+
51: {"name": "个人", "group": "工作表", "parent": SWITCH_PARENT_STATISTICS},
|
|
1712
|
+
# 视图
|
|
1713
|
+
20: {"name": "公开分享", "group": "视图"},
|
|
1714
|
+
38: {"name": "嵌入链接", "group": "视图"},
|
|
1715
|
+
21: {"name": "导出", "group": "视图"},
|
|
1716
|
+
22: {"name": "快捷编辑", "group": "视图"},
|
|
1717
|
+
25: {"name": "批量操作", "group": "视图"},
|
|
1718
|
+
24: {"name": "编辑", "group": "视图", "parent": SWITCH_PARENT_BATCH},
|
|
1719
|
+
26: {"name": "复制", "group": "视图", "parent": SWITCH_PARENT_BATCH},
|
|
1720
|
+
23: {"name": "系统默认打印", "group": "视图", "parent": SWITCH_PARENT_BATCH},
|
|
1721
|
+
27: {"name": "导出", "group": "视图", "parent": SWITCH_PARENT_BATCH},
|
|
1722
|
+
28: {"name": "删除", "group": "视图", "parent": SWITCH_PARENT_BATCH},
|
|
1723
|
+
29: {"name": "执行自定义动作", "group": "视图", "parent": SWITCH_PARENT_BATCH},
|
|
1724
|
+
# 记录
|
|
1725
|
+
30: {"name": "公开分享", "group": "记录"},
|
|
1726
|
+
52: {"name": "内部访问链接", "group": "记录"},
|
|
1727
|
+
32: {"name": "系统默认打印", "group": "记录"},
|
|
1728
|
+
33: {"name": "附件下载", "group": "记录"},
|
|
1729
|
+
34: {"name": "日志", "group": "记录"},
|
|
1730
|
+
35: {"name": "讨论", "group": "记录"},
|
|
1731
|
+
36: {"name": "复制", "group": "记录"},
|
|
1732
|
+
39: {"name": "删除", "group": "记录"},
|
|
1733
|
+
37: {"name": "重新创建", "group": "记录"},
|
|
1734
|
+
# 审批
|
|
1735
|
+
40: {"name": "系统字段", "group": "审批"},
|
|
1736
|
+
41: {"name": "审批流转详情", "group": "审批"},
|
|
1737
|
+
}
|
|
1738
|
+
|
|
1739
|
+
|
|
1740
|
+
def describe_switch(switch_type: int) -> dict[str, Any]:
|
|
1741
|
+
"""Name, group and parent of a switch number; empty-ish when unknown."""
|
|
1742
|
+
meta = SWITCH_TYPES.get(switch_type)
|
|
1743
|
+
if not meta:
|
|
1744
|
+
return {"type": switch_type, "name": "", "group": "", "path": ""}
|
|
1745
|
+
parent = meta.get("parent")
|
|
1746
|
+
path = " > ".join(
|
|
1747
|
+
[meta["group"]]
|
|
1748
|
+
+ ([SWITCH_TYPES[parent]["name"]] if parent in SWITCH_TYPES else [])
|
|
1749
|
+
+ [meta["name"]]
|
|
1750
|
+
)
|
|
1751
|
+
out = {"type": switch_type, "name": meta["name"], "group": meta["group"],
|
|
1752
|
+
"path": path}
|
|
1753
|
+
if parent:
|
|
1754
|
+
out["parent"] = parent
|
|
1755
|
+
return out
|
|
1756
|
+
|
|
1757
|
+
|
|
1758
|
+
def annotate_switches(switches: Any) -> list[dict[str, Any]]:
|
|
1759
|
+
"""Return the switch list with name/group/path added to each entry."""
|
|
1760
|
+
out = []
|
|
1761
|
+
for entry in switches or []:
|
|
1762
|
+
if not isinstance(entry, dict):
|
|
1763
|
+
continue
|
|
1764
|
+
merged = dict(entry)
|
|
1765
|
+
merged.update(describe_switch(entry.get("type")))
|
|
1766
|
+
out.append(merged)
|
|
1767
|
+
return out
|
|
1768
|
+
|
|
1769
|
+
|
|
1770
|
+
def _switch_wire_fields(entry: dict[str, Any]) -> dict[str, Any]:
|
|
1771
|
+
"""The subset of a switch the page sends back on save.
|
|
1772
|
+
|
|
1773
|
+
``FunctionalSwitch/index.jsx`` picks ``roleType/type/state/viewIds``;
|
|
1774
|
+
``displayFlowChart`` rides along where the switch carries one.
|
|
1775
|
+
"""
|
|
1776
|
+
out = {k: entry[k] for k in ("roleType", "type", "state", "viewIds")
|
|
1777
|
+
if k in entry}
|
|
1778
|
+
if "displayFlowChart" in entry:
|
|
1779
|
+
out["displayFlowChart"] = entry["displayFlowChart"]
|
|
1780
|
+
out.setdefault("type", entry.get("type"))
|
|
1781
|
+
out.setdefault("roleType", 0)
|
|
1782
|
+
return out
|
|
1783
|
+
|
|
1784
|
+
|
|
1785
|
+
def _apply_switch_linkage(
|
|
1786
|
+
current: list[dict[str, Any]], changes: dict[int, bool],
|
|
1787
|
+
) -> dict[int, bool]:
|
|
1788
|
+
"""Expand ``changes`` with the parent/child rules the page applies.
|
|
1789
|
+
|
|
1790
|
+
* Turning the 批量操作 (25) or 统计 (500) parent on or off carries all
|
|
1791
|
+
of its children with it.
|
|
1792
|
+
* When every 统计 child ends up off, the parent goes off too. The
|
|
1793
|
+
批量操作 children deliberately do **not** close their parent — the
|
|
1794
|
+
page only does this for 统计 (``index.jsx:108-123``; its comment
|
|
1795
|
+
says 批量操作 but the code targets the statistics parent).
|
|
1796
|
+
"""
|
|
1797
|
+
resolved = dict(changes)
|
|
1798
|
+
for parent, children in (
|
|
1799
|
+
(SWITCH_PARENT_BATCH, _SWITCH_BATCH_CHILDREN),
|
|
1800
|
+
(SWITCH_PARENT_STATISTICS, _SWITCH_STATISTICS_CHILDREN),
|
|
1801
|
+
):
|
|
1802
|
+
if parent in resolved:
|
|
1803
|
+
for child in children:
|
|
1804
|
+
resolved.setdefault(child, resolved[parent])
|
|
1805
|
+
states = {e.get("type"): e.get("state") for e in current
|
|
1806
|
+
if isinstance(e, dict)}
|
|
1807
|
+
states.update(resolved)
|
|
1808
|
+
if any(c in resolved for c in _SWITCH_STATISTICS_CHILDREN):
|
|
1809
|
+
if not any(states.get(c) for c in _SWITCH_STATISTICS_CHILDREN):
|
|
1810
|
+
resolved.setdefault(SWITCH_PARENT_STATISTICS, False)
|
|
1811
|
+
return resolved
|
|
1812
|
+
|
|
1813
|
+
|
|
1814
|
+
def batch_edit_switch(
|
|
1815
|
+
session: Session,
|
|
1816
|
+
worksheet_id: str,
|
|
1817
|
+
changes: dict[int, bool],
|
|
1818
|
+
*,
|
|
1819
|
+
verify: bool = True,
|
|
1820
|
+
) -> dict[str, Any]:
|
|
1821
|
+
"""Apply several switch changes to one worksheet in a single call.
|
|
1822
|
+
|
|
1823
|
+
This is what the feature-switch page itself uses
|
|
1824
|
+
(``Worksheet/BatchEditSwitch``, ``src/api/worksheet.js:2346-2356``):
|
|
1825
|
+
it submits the **whole** ``switchList``, so the untouched switches
|
|
1826
|
+
have to be read first and sent back unchanged or they are lost.
|
|
1827
|
+
|
|
1828
|
+
Parent/child linkage is applied for you (see
|
|
1829
|
+
:func:`_apply_switch_linkage`). With ``verify`` the new state is read
|
|
1830
|
+
back and any switch that did not take is reported, because the
|
|
1831
|
+
endpoint answers ``true`` regardless.
|
|
1832
|
+
|
|
1833
|
+
Returns ``{"worksheetId", "applied": {type: bool}, "failed": {type:
|
|
1834
|
+
{"expected", "actual"}}}``.
|
|
1835
|
+
"""
|
|
1836
|
+
current = get_switches(session, worksheet_id) or []
|
|
1837
|
+
if not isinstance(current, list):
|
|
1838
|
+
raise APIError(
|
|
1839
|
+
f"could not read the switches of worksheet {worksheet_id}")
|
|
1840
|
+
resolved = _apply_switch_linkage(current, {int(k): bool(v)
|
|
1841
|
+
for k, v in changes.items()})
|
|
1842
|
+
switch_list = []
|
|
1843
|
+
for entry in current:
|
|
1844
|
+
if not isinstance(entry, dict):
|
|
1845
|
+
continue
|
|
1846
|
+
wire = _switch_wire_fields(entry)
|
|
1847
|
+
if entry.get("type") in resolved:
|
|
1848
|
+
wire["state"] = resolved[entry["type"]]
|
|
1849
|
+
switch_list.append(wire)
|
|
1850
|
+
known = {e.get("type") for e in current if isinstance(e, dict)}
|
|
1851
|
+
for stype, state in resolved.items():
|
|
1852
|
+
if stype not in known:
|
|
1853
|
+
switch_list.append({"type": stype, "state": state, "roleType": 0})
|
|
1854
|
+
session.api_call(
|
|
1855
|
+
"Worksheet", "BatchEditSwitch",
|
|
1856
|
+
{"worksheetId": worksheet_id, "switchList": switch_list},
|
|
1857
|
+
)
|
|
1858
|
+
result: dict[str, Any] = {"worksheetId": worksheet_id,
|
|
1859
|
+
"applied": dict(resolved), "failed": {}}
|
|
1860
|
+
if verify:
|
|
1861
|
+
after = {e.get("type"): e.get("state")
|
|
1862
|
+
for e in (get_switches(session, worksheet_id) or [])
|
|
1863
|
+
if isinstance(e, dict)}
|
|
1864
|
+
for stype, want in resolved.items():
|
|
1865
|
+
if bool(after.get(stype)) != bool(want):
|
|
1866
|
+
result["failed"][stype] = {"expected": want,
|
|
1867
|
+
"actual": after.get(stype)}
|
|
1868
|
+
return result
|
|
1869
|
+
|
|
1870
|
+
|
|
1684
1871
|
def edit_switch(
|
|
1685
1872
|
session: Session, worksheet_id: str, switch_id: int, value: bool,
|
|
1686
1873
|
) -> dict[str, Any]:
|
|
1687
|
-
"""
|
|
1688
|
-
|
|
1689
|
-
|
|
1690
|
-
|
|
1691
|
-
|
|
1874
|
+
"""Turn one feature switch on or off, and confirm it stuck.
|
|
1875
|
+
|
|
1876
|
+
``EditSwitch`` names the boolean ``state`` and the switch number
|
|
1877
|
+
``type`` (pd-openweb ``src/api/worksheet.js:2330-2343``). This wrapper
|
|
1878
|
+
used to send ``{state: <switch number>, value: <bool>}`` — the two
|
|
1879
|
+
swapped, plus a ``value`` field the API does not define. The server
|
|
1880
|
+
accepts that quietly and answers ``{"data": true}`` without changing
|
|
1881
|
+
anything, so every edit looked like it worked and none of them did.
|
|
1882
|
+
|
|
1883
|
+
The switch's remaining fields are sent back as they are, the way the
|
|
1884
|
+
page does (``_.pick(o, ['roleType', 'type', 'state', 'viewIds'])`` in
|
|
1885
|
+
``FormSet/containers/FunctionalSwitch/index.jsx``); dropping
|
|
1886
|
+
``viewIds`` would reset a view-scoped switch.
|
|
1887
|
+
|
|
1888
|
+
Because a stale payload fails silently rather than loudly, the new
|
|
1889
|
+
state is read back and a write that did not land raises instead of
|
|
1890
|
+
reporting success.
|
|
1891
|
+
"""
|
|
1892
|
+
current = {}
|
|
1893
|
+
for entry in get_switches(session, worksheet_id) or []:
|
|
1894
|
+
if isinstance(entry, dict) and entry.get("type") == switch_id:
|
|
1895
|
+
current = entry
|
|
1896
|
+
break
|
|
1897
|
+
payload: dict[str, Any] = {"worksheetId": worksheet_id,
|
|
1898
|
+
"type": switch_id, "state": value}
|
|
1899
|
+
for field in ("roleType", "viewIds", "displayFlowChart"):
|
|
1900
|
+
if field in current:
|
|
1901
|
+
payload[field] = current[field]
|
|
1902
|
+
payload.setdefault("roleType", 0)
|
|
1903
|
+
result = session.api_call("Worksheet", "EditSwitch", payload)
|
|
1904
|
+
applied = None
|
|
1905
|
+
for entry in get_switches(session, worksheet_id) or []:
|
|
1906
|
+
if isinstance(entry, dict) and entry.get("type") == switch_id:
|
|
1907
|
+
applied = entry.get("state")
|
|
1908
|
+
break
|
|
1909
|
+
if bool(applied) != bool(value):
|
|
1910
|
+
raise APIError(
|
|
1911
|
+
f"switch {switch_id} is still {applied!r} after asking for "
|
|
1912
|
+
f"{value!r} — the server reported success but did not apply it"
|
|
1913
|
+
)
|
|
1914
|
+
return result
|
|
1692
1915
|
|
|
1693
1916
|
|
|
1694
1917
|
# ── Logs ─────────────────────────────────────────────────────────────────
|
|
@@ -3969,6 +3969,18 @@
|
|
|
3969
3969
|
"en": "Raw JSON array of new controls to append. Each item matches the AddWorksheetControls `controls` schema (same shape as `worksheet fields --raw` output).",
|
|
3970
3970
|
"zh-Hans": "要追加的新字段原始 JSON 数组。每一项遵循 AddWorksheetControls 的 `controls` 结构(与 `worksheet fields --raw` 输出形态相同)"
|
|
3971
3971
|
},
|
|
3972
|
+
"worksheet batch-edit-switch": {
|
|
3973
|
+
"en": "Change several feature switches on one or more worksheets.\n\nThe switches you do not name keep their current values. Turning the\n批量操作 or 统计 parent on or off carries its children with it. Each\nworksheet is read back after saving, and any switch that did not take\nis reported rather than counted as success.",
|
|
3974
|
+
"zh-Hans": "批量修改一张或多张工作表的功能开关。\n\n未指定的开关保持原值。开启或关闭「批量操作」「统计」父开关时,其子项会随之联动。每张表保存后都会回读校验,未生效的开关会被报出而不是计为成功。"
|
|
3975
|
+
},
|
|
3976
|
+
"worksheet batch-edit-switch --dry-run": {
|
|
3977
|
+
"en": "Show what would change on each worksheet, without saving.",
|
|
3978
|
+
"zh-Hans": "仅显示每张工作表将要修改的内容,不实际保存。"
|
|
3979
|
+
},
|
|
3980
|
+
"worksheet batch-edit-switch --set": {
|
|
3981
|
+
"en": "Switch to change as TYPE=true|false (repeatable). Numbers come from `hap worksheet switch-types`.",
|
|
3982
|
+
"zh-Hans": "要修改的开关,格式为 TYPE=true|false(可重复指定)。开关号见 `hap worksheet switch-types`。"
|
|
3983
|
+
},
|
|
3972
3984
|
"worksheet chart": {
|
|
3973
3985
|
"en": "Worksheet chart (统计图) management.",
|
|
3974
3986
|
"zh-Hans": "工作表统计图管理"
|
|
@@ -4198,8 +4210,8 @@
|
|
|
4198
4210
|
"zh-Hans": "跳过确认"
|
|
4199
4211
|
},
|
|
4200
4212
|
"worksheet edit-switch": {
|
|
4201
|
-
"en": "
|
|
4202
|
-
"zh-Hans": "
|
|
4213
|
+
"en": "Turn one feature switch on or off.\n\nSWITCH_ID is a switch number from `hap worksheet switch-types`. The\nnew state is read back, so a change that did not take is reported as\nan error instead of success. To change several switches, or the same\nones across many worksheets, use `worksheet batch-edit-switch`.",
|
|
4214
|
+
"zh-Hans": "开启或关闭单个功能开关。\n\nSWITCH_ID 为开关号,可用 `hap worksheet switch-types` 查看。修改后会回读校验,未生效将报错而不是报成功。要一次改多个开关、或对多张工作表改同一批开关,请用 `worksheet batch-edit-switch`。"
|
|
4203
4215
|
},
|
|
4204
4216
|
"worksheet field-types": {
|
|
4205
4217
|
"en": "Show field type constants as a reference table.\n\nLists the builder-style CODE names (Text/Number/Relation/...) you\npass via 'update-fields --fields' alongside the wire ControlTypeId\nand the legacy SCREAMING_SNAKE alias still accepted for backward\ncompatibility. Region maps to three ControlTypeIds — pass\n'regionLevel' in the field spec's `extra` to pick one.",
|
|
@@ -4705,9 +4717,17 @@
|
|
|
4705
4717
|
"en": "Rule type: 0=interact 1=validate 2=lock",
|
|
4706
4718
|
"zh-Hans": "规则类型:0=交互 1=校验 2=锁定"
|
|
4707
4719
|
},
|
|
4720
|
+
"worksheet switch-types": {
|
|
4721
|
+
"en": "Show the feature switch numbers with their names and groups.",
|
|
4722
|
+
"zh-Hans": "显示功能开关号及其对应的名称和分组。"
|
|
4723
|
+
},
|
|
4708
4724
|
"worksheet switches": {
|
|
4709
|
-
"en": "Get feature switches for a worksheet.",
|
|
4710
|
-
"zh-Hans": "
|
|
4725
|
+
"en": "Get feature switches for a worksheet.\n\nEach switch is listed with the name and group it has on the feature\nswitch page, because several numbers share a name and only the group\ntells them apart.",
|
|
4726
|
+
"zh-Hans": "获取工作表的功能开关。每个开关会附带它在功能开关页面上的名称和分组——因为多个开关号共用同一名称,只有分组能区分它们。"
|
|
4727
|
+
},
|
|
4728
|
+
"worksheet switches --raw": {
|
|
4729
|
+
"en": "Return the server's switch list unannotated.",
|
|
4730
|
+
"zh-Hans": "返回服务端原始开关列表,不附加名称和分组。"
|
|
4711
4731
|
},
|
|
4712
4732
|
"worksheet update": {
|
|
4713
4733
|
"en": "Update worksheet alias, description, sidebar name, or sidebar icon.\n\n`--name` and `--icon` change how the worksheet appears in the app\nsidebar; both require --app-id so the section can be resolved.",
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
"""Worksheet feature switches (功能开关).
|
|
2
|
+
|
|
3
|
+
`Worksheet/EditSwitch` takes `state` as the **boolean** and `type` as the
|
|
4
|
+
switch number (pd-openweb src/api/worksheet.js:2330-2343). The wrapper used
|
|
5
|
+
to send `{state: <switch number>, value: <bool>}` — the two swapped, plus a
|
|
6
|
+
`value` key the API does not define. The server accepted that and answered
|
|
7
|
+
`{"data": true}` while changing nothing, so every switch edit reported
|
|
8
|
+
success and silently did nothing (reported by a customer configuring 105
|
|
9
|
+
worksheets, reproduced live 2026-09-05).
|
|
10
|
+
|
|
11
|
+
The page sends the switch's other fields back untouched
|
|
12
|
+
(`_.pick(o, ['roleType', 'type', 'state', 'viewIds'])` in
|
|
13
|
+
FormSet/containers/FunctionalSwitch/index.jsx), so a toggle must preserve
|
|
14
|
+
them rather than reset a view-scoped switch to defaults.
|
|
15
|
+
"""
|
|
16
|
+
|
|
17
|
+
from unittest.mock import MagicMock, patch
|
|
18
|
+
|
|
19
|
+
import pytest
|
|
20
|
+
|
|
21
|
+
from hap_cli.core import worksheet as ws_mod
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def _switches(**over):
|
|
25
|
+
base = {"view": [], "state": True, "type": 24, "roleType": 0,
|
|
26
|
+
"viewIds": [], "displayFlowChart": 0}
|
|
27
|
+
base.update(over)
|
|
28
|
+
return [base, {"view": [], "state": True, "type": 26, "roleType": 0,
|
|
29
|
+
"viewIds": [], "displayFlowChart": 0}]
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
class TestEditSwitch:
|
|
33
|
+
def _session(self, switches=None, after=None):
|
|
34
|
+
session = MagicMock()
|
|
35
|
+
reads = [switches or _switches(), after if after is not None else
|
|
36
|
+
_switches(state=False)]
|
|
37
|
+
session.api_call.side_effect = lambda c, a, p=None, **k: (
|
|
38
|
+
reads.pop(0) if a == "GetSwitch" else {"data": True}
|
|
39
|
+
)
|
|
40
|
+
return session
|
|
41
|
+
|
|
42
|
+
def _edit_payload(self, session):
|
|
43
|
+
for call in session.api_call.call_args_list:
|
|
44
|
+
if call[0][1] == "EditSwitch":
|
|
45
|
+
return call[0][2]
|
|
46
|
+
raise AssertionError("EditSwitch was never called")
|
|
47
|
+
|
|
48
|
+
def test_state_carries_the_boolean_and_type_the_switch_number(self):
|
|
49
|
+
session = self._session()
|
|
50
|
+
ws_mod.edit_switch(session, "ws1", 24, False)
|
|
51
|
+
payload = self._edit_payload(session)
|
|
52
|
+
assert payload["type"] == 24 # the switch, not the state
|
|
53
|
+
assert payload["state"] is False # the boolean, not the switch
|
|
54
|
+
assert "value" not in payload # no such field in the API
|
|
55
|
+
|
|
56
|
+
def test_other_fields_of_the_switch_are_preserved(self):
|
|
57
|
+
session = self._session(
|
|
58
|
+
switches=[{"state": True, "type": 24, "roleType": 2,
|
|
59
|
+
"viewIds": ["v1"], "displayFlowChart": 1}],
|
|
60
|
+
after=[{"state": False, "type": 24, "roleType": 2,
|
|
61
|
+
"viewIds": ["v1"], "displayFlowChart": 1}],
|
|
62
|
+
)
|
|
63
|
+
ws_mod.edit_switch(session, "ws1", 24, False)
|
|
64
|
+
payload = self._edit_payload(session)
|
|
65
|
+
assert payload["roleType"] == 2
|
|
66
|
+
assert payload["viewIds"] == ["v1"]
|
|
67
|
+
assert payload["displayFlowChart"] == 1
|
|
68
|
+
|
|
69
|
+
def test_unknown_switch_still_sends_a_well_formed_payload(self):
|
|
70
|
+
session = self._session(
|
|
71
|
+
switches=[{"state": True, "type": 99}],
|
|
72
|
+
after=[{"state": False, "type": 24}],
|
|
73
|
+
)
|
|
74
|
+
ws_mod.edit_switch(session, "ws1", 24, False)
|
|
75
|
+
payload = self._edit_payload(session)
|
|
76
|
+
assert payload["type"] == 24
|
|
77
|
+
assert payload["state"] is False
|
|
78
|
+
|
|
79
|
+
def test_a_write_that_did_not_stick_is_an_error(self):
|
|
80
|
+
"""The whole point: `true` from the server is not proof."""
|
|
81
|
+
session = self._session(after=_switches(state=True)) # unchanged
|
|
82
|
+
with pytest.raises(Exception) as exc:
|
|
83
|
+
ws_mod.edit_switch(session, "ws1", 24, False)
|
|
84
|
+
assert "24" in str(exc.value)
|
|
85
|
+
|
|
86
|
+
def test_successful_write_is_confirmed_by_reading_back(self):
|
|
87
|
+
session = self._session() # after == state False
|
|
88
|
+
result = ws_mod.edit_switch(session, "ws1", 24, False)
|
|
89
|
+
assert result is True or result == {"data": True} or result
|
|
90
|
+
|
|
91
|
+
|
|
92
|
+
class TestSwitchCatalog:
|
|
93
|
+
def test_same_label_is_disambiguated_by_group(self):
|
|
94
|
+
"""26/36 are both 复制 and 28/39 both 删除 — the group separates them."""
|
|
95
|
+
assert ws_mod.describe_switch(26)["group"] == "视图"
|
|
96
|
+
assert ws_mod.describe_switch(36)["group"] == "记录"
|
|
97
|
+
assert ws_mod.describe_switch(26)["name"] == ws_mod.describe_switch(36)["name"]
|
|
98
|
+
assert ws_mod.describe_switch(28)["group"] != ws_mod.describe_switch(39)["group"]
|
|
99
|
+
|
|
100
|
+
def test_batch_children_carry_their_parent_in_the_path(self):
|
|
101
|
+
assert ws_mod.describe_switch(24)["path"] == "视图 > 批量操作 > 编辑"
|
|
102
|
+
assert ws_mod.describe_switch(24)["parent"] == ws_mod.SWITCH_PARENT_BATCH
|
|
103
|
+
|
|
104
|
+
def test_unknown_type_degrades_without_raising(self):
|
|
105
|
+
d = ws_mod.describe_switch(9999)
|
|
106
|
+
assert d["type"] == 9999 and d["name"] == ""
|
|
107
|
+
|
|
108
|
+
def test_annotate_adds_names_to_the_server_list(self):
|
|
109
|
+
out = ws_mod.annotate_switches([{"type": 36, "state": True}])
|
|
110
|
+
assert out[0]["state"] is True
|
|
111
|
+
assert out[0]["group"] == "记录"
|
|
112
|
+
|
|
113
|
+
|
|
114
|
+
class TestBatchEditSwitch:
|
|
115
|
+
def _session(self, current, after=None):
|
|
116
|
+
session = MagicMock()
|
|
117
|
+
reads = [current, after if after is not None else current]
|
|
118
|
+
session.api_call.side_effect = lambda c, a, p=None, **k: (
|
|
119
|
+
reads.pop(0) if a == "GetSwitch" else {"data": True}
|
|
120
|
+
)
|
|
121
|
+
return session
|
|
122
|
+
|
|
123
|
+
def _sent(self, session):
|
|
124
|
+
for call in session.api_call.call_args_list:
|
|
125
|
+
if call[0][1] == "BatchEditSwitch":
|
|
126
|
+
return call[0][2]["switchList"]
|
|
127
|
+
raise AssertionError("BatchEditSwitch was never called")
|
|
128
|
+
|
|
129
|
+
def test_untouched_switches_are_sent_back_unchanged(self):
|
|
130
|
+
current = [{"type": 24, "state": True, "roleType": 0, "viewIds": []},
|
|
131
|
+
{"type": 30, "state": True, "roleType": 2, "viewIds": ["v"]}]
|
|
132
|
+
after = [{"type": 24, "state": False}, {"type": 30, "state": True}]
|
|
133
|
+
session = self._session(current, after)
|
|
134
|
+
ws_mod.batch_edit_switch(session, "ws1", {24: False})
|
|
135
|
+
sent = {s["type"]: s for s in self._sent(session)}
|
|
136
|
+
assert sent[24]["state"] is False
|
|
137
|
+
assert sent[30]["state"] is True # not dropped
|
|
138
|
+
assert sent[30]["viewIds"] == ["v"] # and not reset
|
|
139
|
+
|
|
140
|
+
def test_turning_off_the_batch_parent_closes_its_children(self):
|
|
141
|
+
current = [{"type": 25, "state": True}, {"type": 24, "state": True},
|
|
142
|
+
{"type": 26, "state": True}]
|
|
143
|
+
after = [{"type": 25, "state": False}, {"type": 24, "state": False},
|
|
144
|
+
{"type": 26, "state": False}]
|
|
145
|
+
session = self._session(current, after)
|
|
146
|
+
res = ws_mod.batch_edit_switch(session, "ws1", {25: False})
|
|
147
|
+
assert res["applied"][24] is False and res["applied"][26] is False
|
|
148
|
+
|
|
149
|
+
def test_batch_children_do_not_close_their_parent(self):
|
|
150
|
+
"""Only 统计 closes its parent; 批量操作 does not (index.jsx:108-123)."""
|
|
151
|
+
current = [{"type": 25, "state": True}] + [
|
|
152
|
+
{"type": t, "state": True} for t in (24, 26, 23, 27, 28, 29)]
|
|
153
|
+
after = [{"type": 25, "state": True}] + [
|
|
154
|
+
{"type": t, "state": False} for t in (24, 26, 23, 27, 28, 29)]
|
|
155
|
+
session = self._session(current, after)
|
|
156
|
+
res = ws_mod.batch_edit_switch(
|
|
157
|
+
session, "ws1", {t: False for t in (24, 26, 23, 27, 28, 29)})
|
|
158
|
+
assert 25 not in res["applied"]
|
|
159
|
+
|
|
160
|
+
def test_last_statistics_child_off_also_closes_the_parent(self):
|
|
161
|
+
current = [{"type": 500, "state": True}, {"type": 50, "state": True},
|
|
162
|
+
{"type": 51, "state": False}]
|
|
163
|
+
after = [{"type": 500, "state": False}, {"type": 50, "state": False},
|
|
164
|
+
{"type": 51, "state": False}]
|
|
165
|
+
session = self._session(current, after)
|
|
166
|
+
res = ws_mod.batch_edit_switch(session, "ws1", {50: False})
|
|
167
|
+
assert res["applied"][500] is False
|
|
168
|
+
|
|
169
|
+
def test_a_switch_that_did_not_take_is_reported_not_swallowed(self):
|
|
170
|
+
current = [{"type": 24, "state": True}]
|
|
171
|
+
after = [{"type": 24, "state": True}] # server ignored it
|
|
172
|
+
session = self._session(current, after)
|
|
173
|
+
res = ws_mod.batch_edit_switch(session, "ws1", {24: False})
|
|
174
|
+
assert res["failed"][24] == {"expected": False, "actual": True}
|
|
@@ -230,6 +230,7 @@ hap_cli/tests/test_workflow_read_write_keys.py
|
|
|
230
230
|
hap_cli/tests/test_workflow_v3_semantic_cli.py
|
|
231
231
|
hap_cli/tests/test_worksheet_create_section.py
|
|
232
232
|
hap_cli/tests/test_worksheet_crud_cli.py
|
|
233
|
+
hap_cli/tests/test_worksheet_switch.py
|
|
233
234
|
hap_cli/tests/test_worksheet_v3_semantic_cli.py
|
|
234
235
|
hap_cli/utils/__init__.py
|
|
235
236
|
hap_cli/utils/formatting.py
|
|
File without changes
|
|
File without changes
|
|
File without changes
|