ccs-digitalmarketplace-frameworks 3.0.3 → 3.2.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.
Files changed (79) hide show
  1. package/README.md +4 -1
  2. package/frameworks/digital-outcomes-and-specialists-7/manifests/edit_service.yml +40 -6
  3. package/frameworks/digital-outcomes-and-specialists-7/manifests/edit_service_as_admin.yml +43 -6
  4. package/frameworks/digital-outcomes-and-specialists-7/manifests/edit_submission.yml +47 -6
  5. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefDataOfficerBasicDayRate.yml +26 -0
  6. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefDataOfficerBasicDayRateSpecialist.yml +26 -0
  7. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefDataOfficerBasicLocations.yml +32 -0
  8. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefDataOfficerBasicOffshoreDayRate.yml +27 -0
  9. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefDataOfficerBasicQuantity.yml +22 -0
  10. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefDataOfficerBasicSecurityClearance.yml +23 -0
  11. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefDataOfficerBasicUkDayRate.yml +26 -0
  12. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefDataOfficerRoles.yml +19 -0
  13. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefInformationSecurityOfficerBasicDayRate.yml +26 -0
  14. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefInformationSecurityOfficerBasicDayRateSpecialist.yml +26 -0
  15. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefInformationSecurityOfficerBasicLocations.yml +32 -0
  16. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefInformationSecurityOfficerBasicOffshoreDayRate.yml +27 -0
  17. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefInformationSecurityOfficerBasicQuantity.yml +22 -0
  18. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefInformationSecurityOfficerBasicSecurityClearance.yml +23 -0
  19. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefInformationSecurityOfficerBasicUkDayRate.yml +26 -0
  20. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefInformationSecurityOfficerRoles.yml +19 -0
  21. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefTechnologyOfficerBasicDayRate.yml +26 -0
  22. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefTechnologyOfficerBasicDayRateSpecialist.yml +26 -0
  23. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefTechnologyOfficerBasicLocations.yml +32 -0
  24. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefTechnologyOfficerBasicOffshoreDayRate.yml +27 -0
  25. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefTechnologyOfficerBasicQuantity.yml +22 -0
  26. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefTechnologyOfficerBasicSecurityClearance.yml +23 -0
  27. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefTechnologyOfficerBasicUkDayRate.yml +26 -0
  28. package/frameworks/digital-outcomes-and-specialists-7/questions/services/chiefDigitalAndDataRolesChiefTechnologyOfficerRoles.yml +19 -0
  29. package/frameworks/digital-outcomes-and-specialists-7/questions/services/complyWithMrsCodeOfConduct.yml +19 -0
  30. package/frameworks/digital-outcomes-and-specialists-7/questions/services/icoRegistered.yml +21 -0
  31. package/frameworks/digital-outcomes-and-specialists-7/questions/services/icoRegistrationReference.yml +16 -0
  32. package/frameworks/digital-outcomes-and-specialists-7/questions/services/mrsMember.yml +18 -0
  33. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefDataOfficer.yml +19 -0
  34. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefDataOfficerBasic.yml +20 -0
  35. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefDataOfficerBasicDCDP.yml +20 -0
  36. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefDataOfficerDCDP.yml +14 -0
  37. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefInformationSecurityOfficer.yml +19 -0
  38. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefInformationSecurityOfficerBasic.yml +20 -0
  39. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefInformationSecurityOfficerBasicDCDP.yml +20 -0
  40. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefInformationSecurityOfficerDCDP.yml +14 -0
  41. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefTechnologyOfficer.yml +19 -0
  42. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefTechnologyOfficerBasic.yml +20 -0
  43. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefTechnologyOfficerBasicDCDP.yml +20 -0
  44. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqChiefDigitalAndDataRolesChiefTechnologyOfficerDCDP.yml +14 -0
  45. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqIcoRegistered.yml +17 -0
  46. package/frameworks/digital-outcomes-and-specialists-7/questions/services/multiqOtherTradeOrIndustryBodies.yml +17 -0
  47. package/frameworks/digital-outcomes-and-specialists-7/questions/services/otherTradeOrIndustryBodies.yml +29 -0
  48. package/frameworks/digital-outcomes-and-specialists-7/questions/services/otherTradeOrIndustryBodiesOther.yml +18 -0
  49. package/frameworks/digital-outcomes-and-specialists-7/questions/services/rasAccredited.yml +18 -0
  50. package/frameworks/digital-outcomes-and-specialists-7/questions/services/useRasAccreditedSubcontractors.yml +18 -0
  51. package/frameworks/digital-outcomes-and-specialists-7/questions/services/userResearchParticipantsEssentialsManage.yml +22 -0
  52. package/frameworks/digital-outcomes-and-specialists-7/questions/services/userResearchParticipantsEssentialsRecruit.yml +22 -0
  53. package/frameworks/digital-outcomes-and-specialists-7/questions/services/whatTypesOfUserResearchStudio.yml +24 -0
  54. package/frameworks/digital-outcomes-and-specialists-7/questions/services/whatUserResearchServices.yml +46 -0
  55. package/package.json +1 -1
  56. package/requirements-dev.txt +2 -2
  57. package/requirements.in +1 -1
  58. package/requirements.txt +1 -1
  59. package/schema_generator/validation.py +89 -17
  60. package/schemas/questions.json +23 -0
  61. package/tests/test_content.py +43 -0
  62. package/tests/test_generate_validation_schema.py +86 -2
  63. package/assets/style.css +0 -186
  64. package/assets/tests_test_generate_validation_schema.py__test_boolean_list_property_0_0.txt +0 -11
  65. package/assets/tests_test_generate_validation_schema.py__test_boolean_property_with_required_value_0_0.txt +0 -11
  66. package/assets/tests_test_generate_validation_schema.py__test_checkbox_property_0_0.txt +0 -14
  67. package/assets/tests_test_generate_validation_schema.py__test_checkbox_tree_property_0_0.txt +0 -13
  68. package/assets/tests_test_generate_validation_schema.py__test_list_property_0_0.txt +0 -11
  69. package/assets/tests_test_generate_validation_schema.py__test_number_property_0_0.txt +0 -11
  70. package/assets/tests_test_generate_validation_schema.py__test_number_property_limits_0_0.txt +0 -11
  71. package/assets/tests_test_generate_validation_schema.py__test_optional_text_property_0_0.txt +0 -11
  72. package/assets/tests_test_generate_validation_schema.py__test_parse_question_limits_char_count_0_0.txt +0 -11
  73. package/assets/tests_test_generate_validation_schema.py__test_parse_question_limits_word_count_0_0.txt +0 -11
  74. package/assets/tests_test_generate_validation_schema.py__test_parse_question_limits_word_count_optional_0_0.txt +0 -11
  75. package/assets/tests_test_generate_validation_schema.py__test_radios_property_0_0.txt +0 -14
  76. package/assets/tests_test_generate_validation_schema.py__test_text_property_0_0.txt +0 -11
  77. package/assets/tests_test_generate_validation_schema.py__test_text_property_format_0_0.txt +0 -11
  78. package/assets/tests_test_generate_validation_schema.py__test_uri_property_0_0.txt +0 -11
  79. package/frameworks/digital-outcomes-and-specialists-7/questions/services/serviceName.yml +0 -18
@@ -0,0 +1,22 @@
1
+ name: Recruit participants without revealing details
2
+ question: Can you, if asked, recruit participants without revealing details of the organisation you’re recruiting for?
3
+
4
+ depends:
5
+ - "on": lot
6
+ being:
7
+ - user-research-studios-and-participants
8
+
9
+ depends_on_follow_up:
10
+ whatUserResearchServices:
11
+ - user-research-participants
12
+
13
+ type: checkboxes
14
+
15
+ options:
16
+ - label: "Yes"
17
+ value: "yes"
18
+
19
+ validations:
20
+ -
21
+ name: answer_required
22
+ message: Select if you can recruit participants without revealing details of the organisation you’re recruiting for
@@ -0,0 +1,24 @@
1
+ name: What types of user research studio can you provide?
2
+ question: What types of user research studio can you provide?
3
+
4
+ depends:
5
+ - "on": lot
6
+ being:
7
+ - user-research-studios-and-participants
8
+
9
+ type: checkboxes
10
+
11
+ options:
12
+ - label: "Virtual studio"
13
+ value: "virtual-studio"
14
+ - label: "Physical location"
15
+ value: "physical-location"
16
+
17
+ depends_on_follow_up:
18
+ whatUserResearchServices:
19
+ - user-research-studios
20
+
21
+ validations:
22
+ -
23
+ name: answer_required
24
+ message: You must select the types of user research studio can you provide
@@ -0,0 +1,46 @@
1
+ name: What services can you provide?
2
+ question: What services can you provide?
3
+
4
+ depends:
5
+ - "on": lot
6
+ being:
7
+ - user-research-studios-and-participants
8
+
9
+ type: checkboxes
10
+
11
+ options:
12
+ - label: "User research studios"
13
+ value: "user-research-studios"
14
+ - label: "User research participants"
15
+ value: "user-research-participants"
16
+
17
+ dependent_follow_up:
18
+ userResearchParticipantsEssentialsRecruit:
19
+ - user-research-participants
20
+ userResearchParticipantsEssentialsManage:
21
+ - user-research-participants
22
+ mrsMember:
23
+ - user-research-participants
24
+ otherTradeOrIndustryBodies:
25
+ - user-research-participants
26
+ rasAccredited:
27
+ - user-research-participants
28
+ useRasAccreditedSubcontractors:
29
+ - user-research-participants
30
+ complyWithMrsCodeOfConduct:
31
+ - user-research-participants
32
+ icoRegistered:
33
+ - user-research-participants
34
+ whatTypesOfUserResearchStudio:
35
+ - user-research-studios
36
+
37
+ dependent_follow_up_followup:
38
+ otherTradeOrIndustryBodiesOther:
39
+ - user-research-participants
40
+ icoRegistrationReference:
41
+ - user-research-participants
42
+
43
+ validations:
44
+ -
45
+ name: answer_required
46
+ message: You must select the services you can provide
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "ccs-digitalmarketplace-frameworks",
3
- "version": "3.0.3",
3
+ "version": "3.2.0",
4
4
  "description": "Data files for Digital Marketplace’s procurement frameworks",
5
5
  "repository": "git@github.com:Crown-Commercial-Service/ccs-digitalmarketplace-frameworks",
6
6
  "author": "enquiries@digitalmarketplace.service.gov.uk"
@@ -34,7 +34,7 @@ ccs-digitalmarketplace-apiclient==25.11.0
34
34
  # via
35
35
  # -r requirements.txt
36
36
  # ccs-digitalmarketplace-utils
37
- ccs-digitalmarketplace-content-loader==11.6.0
37
+ ccs-digitalmarketplace-content-loader==11.10.0
38
38
  # via -r requirements.txt
39
39
  ccs-digitalmarketplace-utils==68.0.0
40
40
  # via
@@ -115,7 +115,7 @@ govuk-country-register==0.5.0
115
115
  # via
116
116
  # -r requirements.txt
117
117
  # ccs-digitalmarketplace-utils
118
- hypothesis==6.112.4
118
+ hypothesis==6.115.0
119
119
  # via -r requirements-dev.in
120
120
  idna==3.7
121
121
  # via
package/requirements.in CHANGED
@@ -1,5 +1,5 @@
1
1
  docopt==0.6.2
2
2
  deepmerge==2.0
3
3
  python-json-logger==2.0.7
4
- ccs-digitalmarketplace-content-loader==11.6.0
4
+ ccs-digitalmarketplace-content-loader==11.10.0
5
5
  ccs-digitalmarketplace-utils==68.0.0
package/requirements.txt CHANGED
@@ -18,7 +18,7 @@ cachelib==0.13.0
18
18
  # via flask-session
19
19
  ccs-digitalmarketplace-apiclient==25.11.0
20
20
  # via ccs-digitalmarketplace-utils
21
- ccs-digitalmarketplace-content-loader==11.6.0
21
+ ccs-digitalmarketplace-content-loader==11.10.0
22
22
  # via -r requirements.in
23
23
  ccs-digitalmarketplace-utils==68.0.0
24
24
  # via
@@ -280,7 +280,8 @@ def load_questions(schema_type, framework_slug, lot_slug_or_agreement_version):
280
280
 
281
281
  manifest = loader.get_manifest(framework_slug, MANIFESTS[schema_type]['manifest']).filter(
282
282
  {filter_key: lot_slug_or_agreement_version},
283
- dynamic=False
283
+ dynamic=False,
284
+ ignore_depends_on_follow_up=True
284
285
  )
285
286
 
286
287
  return {q['id']: q for q in sum((s.questions for s in manifest.sections), [])}
@@ -348,22 +349,41 @@ def uri_property(question):
348
349
  }}
349
350
 
350
351
 
352
+ def has_dependent_follow_up(property_func):
353
+ """
354
+ Adds the additional dependent follow up schema to the property
355
+ """
356
+ def inner_func(question):
357
+ schema = property_func(question)
358
+ schema_addition = {}
359
+
360
+ if question.get('dependent_follow_up'):
361
+ schema_addition = _dependent_follow_up(question)
362
+
363
+ return schema, schema_addition
364
+
365
+ return inner_func
366
+
367
+
368
+ @has_dependent_follow_up
351
369
  def checkbox_property(question):
352
370
  """
353
371
  Convert a checkbox question into JSON Schema.
354
372
  """
355
- return {question['id']: {
356
- "type": "array",
357
- "uniqueItems": True,
358
- "minItems": 0 if question.get('optional') else 1,
359
- "maxItems": question.get('number_of_items', len(question['options'])),
360
- "items": {
361
- "enum": [
362
- option.get('value', option['label'])
363
- for option in question['options']
364
- ]
373
+ return {
374
+ question['id']: {
375
+ "type": "array",
376
+ "uniqueItems": True,
377
+ "minItems": 0 if question.get('optional') else 1,
378
+ "maxItems": question.get('number_of_items', len(question['options'])),
379
+ "items": {
380
+ "enum": [
381
+ option.get('value', option['label'])
382
+ for option in question['options']
383
+ ]
384
+ }
365
385
  }
366
- }}
386
+ }
367
387
 
368
388
 
369
389
  def checkbox_tree_property(question):
@@ -625,6 +645,45 @@ def _followup(question, root):
625
645
  return {'allOf': schemas}
626
646
 
627
647
 
648
+ def _dependent_follow_up(question):
649
+ schemas = []
650
+ question_values = [
651
+ option.get('value', option['label'])
652
+ for option in question.options
653
+ ]
654
+ for dependent_follow_up_field, values in question["dependent_follow_up"].items():
655
+ non_dependent_question_values = [
656
+ value
657
+ for value in question_values
658
+ if value not in values
659
+ ]
660
+ should_not_have_followup = {
661
+ "items": {"enum": non_dependent_question_values}
662
+ }
663
+ should_have_followup = {
664
+ "not": {"items": {"enum": non_dependent_question_values}}
665
+ }
666
+
667
+ schemas.append({
668
+ 'oneOf': [
669
+ {
670
+ "properties": {
671
+ question['id']: should_not_have_followup,
672
+ dependent_follow_up_field: {"type": "null"}
673
+ },
674
+ },
675
+ {
676
+ "properties": {
677
+ question['id']: should_have_followup,
678
+ },
679
+ "required": [dependent_follow_up_field]
680
+ },
681
+ ]
682
+ })
683
+
684
+ return {'allOf': schemas}
685
+
686
+
628
687
  def _flat_multiquestion(question):
629
688
  properties = {}
630
689
  for nested_question in question.questions:
@@ -666,7 +725,7 @@ def _nested_multiquestion(question):
666
725
 
667
726
 
668
727
  def _flat_multiquestion_required(question):
669
- if question.get('optional'):
728
+ if question.get('optional') or question.get('depends_on_follow_up'):
670
729
  return []
671
730
 
672
731
  return _nested_multiquestion_required(question)
@@ -825,8 +884,13 @@ def add_assurance(value_schema, assurance_approach):
825
884
  def build_question_properties(question):
826
885
  question_data = QUESTION_TYPES[question['type']](question)
827
886
  if question.get('assuranceApproach'):
828
- for key, value_schema in question_data.items():
829
- question_data[key] = add_assurance(value_schema, question['assuranceApproach'])
887
+ if isinstance(question_data, tuple):
888
+ for key, value_schema in question_data[0].items():
889
+ question_data[0][key] = add_assurance(value_schema, question['assuranceApproach'])
890
+ else:
891
+ for key, value_schema in question_data.items():
892
+ question_data[key] = add_assurance(value_schema, question['assuranceApproach'])
893
+
830
894
  return question_data
831
895
 
832
896
 
@@ -843,10 +907,18 @@ def build_schema_properties(schema, questions):
843
907
  if isinstance(property_schema, tuple):
844
908
  property_schema, schema_addition = property_schema
845
909
  schema['properties'].update(property_schema)
846
- schema = merge_schemas(schema, schema_addition)
910
+
911
+ if (
912
+ not schema_addition or question.get('dependent_follow_up')
913
+ ) and not question.get('depends_on_follow_up'):
914
+ schema['required'].extend(question.required_form_fields)
915
+
916
+ if schema_addition:
917
+ schema = merge_schemas(schema, schema_addition)
847
918
  else:
848
919
  schema['properties'].update(property_schema)
849
- schema['required'].extend(question.required_form_fields)
920
+ if not question.get('depends_on_follow_up'):
921
+ schema['required'].extend(question.required_form_fields)
850
922
 
851
923
  schema['required'].sort()
852
924
 
@@ -48,6 +48,11 @@
48
48
  "properties": {
49
49
  "type": {"enum": ["boolean", "radios", "checkboxes"]}
50
50
  }
51
+ },
52
+ "dependent_follow_up": {
53
+ "properties": {
54
+ "type": {"enum": ["checkboxes"]}
55
+ }
51
56
  }
52
57
  },
53
58
  "properties": {
@@ -321,6 +326,24 @@
321
326
  "^[a-zA-Z0-9]+$": {"type": "array"}
322
327
  }
323
328
  },
329
+ "dependent_follow_up": {
330
+ "type": "object",
331
+ "patternProperties": {
332
+ "^[a-zA-Z0-9]+$": {"type": "array"}
333
+ }
334
+ },
335
+ "dependent_follow_up_followup": {
336
+ "type": "object",
337
+ "patternProperties": {
338
+ "^[a-zA-Z0-9]+$": {"type": "array"}
339
+ }
340
+ },
341
+ "depends_on_follow_up": {
342
+ "type": "object",
343
+ "patternProperties": {
344
+ "^[a-zA-Z0-9]+$": {"type": "array"}
345
+ }
346
+ },
324
347
  "dynamic_field": {
325
348
  "type": "string"
326
349
  },
@@ -6,6 +6,7 @@ import pytest
6
6
 
7
7
  from dmcontent import ContentLoader
8
8
  from dmcontent.utils import TemplateField
9
+ from dmcontent.questions import Multiquestion
9
10
 
10
11
  content = ContentLoader('./')
11
12
 
@@ -130,6 +131,48 @@ def test_render_question(framework, question_id, question_set, question):
130
131
  )
131
132
 
132
133
 
134
+ @pytest.mark.parametrize("framework, question_set, manifest", get_manifests())
135
+ def test_dependent_follow_ups(framework, question_set, manifest):
136
+ content.load_manifest(framework, question_set, manifest)
137
+ loaded_content = content.get_manifest(framework, manifest)
138
+
139
+ questions_with_dependent_follow_up = {}
140
+ questions_with_dependent_follow_up_followup = {}
141
+ expected_questions_with_dependent_follow_up = {}
142
+ expected_questions_with_dependent_follow_up_followup = {}
143
+
144
+ for section in loaded_content.sections:
145
+ for section_question in section.questions:
146
+ if isinstance(section_question, Multiquestion):
147
+ questions = section_question.get_questions_flat()
148
+ else:
149
+ questions = [section_question]
150
+
151
+ for question in questions:
152
+ if question.get("dependent_follow_up"):
153
+ questions_with_dependent_follow_up[question.id] = question.get("dependent_follow_up")
154
+ if question.get("dependent_follow_up_followup"):
155
+ questions_with_dependent_follow_up_followup[question.id] = \
156
+ question.get("dependent_follow_up_followup")
157
+
158
+ for question_id, question_values in question.get("depends_on_follow_up", {}).items():
159
+ if question_id not in expected_questions_with_dependent_follow_up:
160
+ expected_questions_with_dependent_follow_up[question_id] = {}
161
+
162
+ for field in question.form_fields:
163
+ expected_questions_with_dependent_follow_up[question_id][field] = question_values
164
+
165
+ for fllowup_question_id in question.get('followup', {}).keys():
166
+ if question_id not in expected_questions_with_dependent_follow_up_followup:
167
+ expected_questions_with_dependent_follow_up_followup[question_id] = {}
168
+
169
+ expected_questions_with_dependent_follow_up_followup[question_id][fllowup_question_id] = \
170
+ question_values
171
+
172
+ assert questions_with_dependent_follow_up == expected_questions_with_dependent_follow_up
173
+ assert questions_with_dependent_follow_up_followup == expected_questions_with_dependent_follow_up_followup
174
+
175
+
133
176
  def strip_paragraph_wrapper(value):
134
177
  match = re.fullmatch(r"^(<p.*?>)(.+?)(</p>)", value)
135
178
  if match:
@@ -274,12 +274,14 @@ def test_checkbox_property(id, options):
274
274
  "id": id,
275
275
  "options": options
276
276
  }
277
- result = checkbox_property(question)
277
+ result, additional_schema = checkbox_property(question)
278
278
  assert result[id]['minItems'] == 1
279
+ assert additional_schema == {}
279
280
 
280
281
  question['optional'] = True
281
- result = checkbox_property(question)
282
+ result, additional_schema = checkbox_property(question)
282
283
  assert result[id]['minItems'] == 0
284
+ assert additional_schema == {}
283
285
 
284
286
  enum = result[id]['items']['enum']
285
287
  assert len(enum) == len(options)
@@ -289,6 +291,88 @@ def test_checkbox_property(id, options):
289
291
  for option in options if 'value' not in option)
290
292
 
291
293
 
294
+ def test_checkbox_property_with_dependent_follow_up():
295
+ options = [
296
+ {"value": "1", "label": "One"},
297
+ {"label": "Two"},
298
+ {"value": "3", "label": "Three"}
299
+ ]
300
+
301
+ question = ContentQuestion({
302
+ "id": "q1",
303
+ "options": options,
304
+ "dependent_follow_up": {
305
+ "a": [
306
+ "1",
307
+ "Two"
308
+ ],
309
+ "b": [
310
+ "Two",
311
+ ],
312
+ "c": [
313
+ "1",
314
+ "3"
315
+ ]
316
+ }
317
+ })
318
+ result, additional_schema = checkbox_property(question)
319
+ assert result['q1']['minItems'] == 1
320
+
321
+ enum = result['q1']['items']['enum']
322
+ assert len(enum) == len(options)
323
+ assert all(option['value'] in enum
324
+ for option in options if 'value' in option)
325
+ assert all(option['label'] in enum
326
+ for option in options if 'value' not in option)
327
+
328
+ assert additional_schema == {
329
+ "allOf": [
330
+ {
331
+ "oneOf": [
332
+ {
333
+ "properties": {
334
+ "q1": {"items": {"enum": ["3"]}},
335
+ "a": {"type": "null"},
336
+ }
337
+ },
338
+ {
339
+ "properties": {"q1": {"not": {"items": {"enum": ["3"]}}}},
340
+ "required": ["a"],
341
+ },
342
+ ]
343
+ },
344
+ {
345
+ "oneOf": [
346
+ {
347
+ "properties": {
348
+ "q1": {"items": {"enum": ["1", "3"]}},
349
+ "b": {"type": "null"},
350
+ }
351
+ },
352
+ {
353
+ "properties": {"q1": {"not": {"items": {"enum": ["1", "3"]}}}},
354
+ "required": ["b"],
355
+ },
356
+ ]
357
+ },
358
+ {
359
+ "oneOf": [
360
+ {
361
+ "properties": {
362
+ "q1": {"items": {"enum": ["Two"]}},
363
+ "c": {"type": "null"},
364
+ }
365
+ },
366
+ {
367
+ "properties": {"q1": {"not": {"items": {"enum": ["Two"]}}}},
368
+ "required": ["c"],
369
+ },
370
+ ]
371
+ },
372
+ ]
373
+ }
374
+
375
+
292
376
  @settings(suppress_health_check=[HealthCheck.too_slow])
293
377
  @given(st.text(), nested_checkboxes_list(), st.integers(min_value=1))
294
378
  def test_checkbox_tree_property(id, options, number_of_items):
package/assets/style.css DELETED
@@ -1,186 +0,0 @@
1
- body {
2
- font-family: Helvetica, Arial, sans-serif;
3
- font-size: 12px;
4
- /* do not increase min-width as some may use split screens */
5
- min-width: 800px;
6
- color: #999;
7
- }
8
-
9
- h1 {
10
- font-size: 24px;
11
- color: black;
12
- }
13
-
14
- h2 {
15
- font-size: 16px;
16
- color: black;
17
- }
18
-
19
- p {
20
- color: black;
21
- }
22
-
23
- a {
24
- color: #999;
25
- }
26
-
27
- table {
28
- border-collapse: collapse;
29
- }
30
-
31
- /******************************
32
- * SUMMARY INFORMATION
33
- ******************************/
34
- #environment td {
35
- padding: 5px;
36
- border: 1px solid #E6E6E6;
37
- }
38
- #environment tr:nth-child(odd) {
39
- background-color: #f6f6f6;
40
- }
41
-
42
- /******************************
43
- * TEST RESULT COLORS
44
- ******************************/
45
- span.passed,
46
- .passed .col-result {
47
- color: green;
48
- }
49
-
50
- span.skipped,
51
- span.xfailed,
52
- span.rerun,
53
- .skipped .col-result,
54
- .xfailed .col-result,
55
- .rerun .col-result {
56
- color: orange;
57
- }
58
-
59
- span.error,
60
- span.failed,
61
- span.xpassed,
62
- .error .col-result,
63
- .failed .col-result,
64
- .xpassed .col-result {
65
- color: red;
66
- }
67
-
68
- /******************************
69
- * RESULTS TABLE
70
- *
71
- * 1. Table Layout
72
- * 2. Extra
73
- * 3. Sorting items
74
- *
75
- ******************************/
76
- /*------------------
77
- * 1. Table Layout
78
- *------------------*/
79
- #results-table {
80
- border: 1px solid #e6e6e6;
81
- color: #999;
82
- font-size: 12px;
83
- width: 100%;
84
- }
85
- #results-table th,
86
- #results-table td {
87
- padding: 5px;
88
- border: 1px solid #E6E6E6;
89
- text-align: left;
90
- }
91
- #results-table th {
92
- font-weight: bold;
93
- }
94
-
95
- /*------------------
96
- * 2. Extra
97
- *------------------*/
98
- .log {
99
- background-color: #e6e6e6;
100
- border: 1px solid #e6e6e6;
101
- color: black;
102
- display: block;
103
- font-family: "Courier New", Courier, monospace;
104
- height: 230px;
105
- overflow-y: scroll;
106
- padding: 5px;
107
- white-space: pre-wrap;
108
- }
109
- .log:only-child {
110
- height: inherit;
111
- }
112
-
113
- div.image {
114
- border: 1px solid #e6e6e6;
115
- float: right;
116
- height: 240px;
117
- margin-left: 5px;
118
- overflow: hidden;
119
- width: 320px;
120
- }
121
- div.image img {
122
- width: 320px;
123
- }
124
-
125
- div.video {
126
- border: 1px solid #e6e6e6;
127
- float: right;
128
- height: 240px;
129
- margin-left: 5px;
130
- overflow: hidden;
131
- width: 320px;
132
- }
133
- div.video video {
134
- overflow: hidden;
135
- width: 320px;
136
- height: 240px;
137
- }
138
-
139
- .collapsed {
140
- display: none;
141
- }
142
-
143
- .expander::after {
144
- content: " (show details)";
145
- color: #BBB;
146
- font-style: italic;
147
- cursor: pointer;
148
- }
149
-
150
- .collapser::after {
151
- content: " (hide details)";
152
- color: #BBB;
153
- font-style: italic;
154
- cursor: pointer;
155
- }
156
-
157
- /*------------------
158
- * 3. Sorting items
159
- *------------------*/
160
- .sortable {
161
- cursor: pointer;
162
- }
163
-
164
- .sort-icon {
165
- font-size: 0px;
166
- float: left;
167
- margin-right: 5px;
168
- margin-top: 5px;
169
- /*triangle*/
170
- width: 0;
171
- height: 0;
172
- border-left: 8px solid transparent;
173
- border-right: 8px solid transparent;
174
- }
175
- .inactive .sort-icon {
176
- /*finish triangle*/
177
- border-top: 8px solid #E6E6E6;
178
- }
179
- .asc.active .sort-icon {
180
- /*finish triangle*/
181
- border-bottom: 8px solid #999;
182
- }
183
- .desc.active .sort-icon {
184
- /*finish triangle*/
185
- border-top: 8px solid #999;
186
- }
@@ -1,11 +0,0 @@
1
- tests/test_generate_validation_schema.py::test_boolean_list_property:
2
-
3
- - during reuse phase (0.00 seconds):
4
- - Typical runtimes: < 1ms, ~ 57% in data generation
5
- - 2 passing examples, 0 failing examples, 0 invalid examples
6
-
7
- - during generate phase (0.11 seconds):
8
- - Typical runtimes: < 1ms, ~ 73% in data generation
9
- - 98 passing examples, 0 failing examples, 1 invalid examples
10
-
11
- - Stopped because settings.max_examples=100