From 9d891631e49572f61cee88355aa38d1ca96b083d Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 25 Sep 2023 15:55:07 -0300 Subject: [PATCH 1/8] =?UTF-8?q?=F0=9F=90=9B=20Improve=20logic=20for=20`pro?= =?UTF-8?q?cess=5Fneedextend`?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #976 --- sphinx_needs/data.py | 14 ++++- sphinx_needs/directives/needextend.py | 81 ++++++++++++++------------- 2 files changed, 53 insertions(+), 42 deletions(-) diff --git a/sphinx_needs/data.py b/sphinx_needs/data.py index 8c20007e5..521c169cb 100644 --- a/sphinx_needs/data.py +++ b/sphinx_needs/data.py @@ -122,9 +122,10 @@ class NeedsInfoType(NeedsBaseDataType): """Hexadecimal color code of the type.""" type_style: str - # Used by needextend is_modified: bool + """Whether the need was modified by needextend.""" modifications: int + """Number of modifications by needextend.""" # parts information parts: dict[str, NeedsPartType] @@ -257,11 +258,20 @@ class NeedsBarType(NeedsBaseDataType): class NeedsExtendType(NeedsBaseDataType): - """Data to modify an existing need.""" + """Data to modify existing need(s).""" filter: None | str + """Single need ID or filter string to select multiple needs.""" modifications: dict[str, str] + """Mapping of field name to new value. + If the field name starts with a ``+``, the new value is appended to the existing value. + If the field name starts with a ``-``, the existing value is cleared (new value is ignored). + """ strict: bool + """If ``filter`` conforms to ``needs_id_regex``, + and is not an existing need ID, + whether to except the build (otherwise log-info message is written). + """ class NeedsFilteredBaseType(NeedsBaseDataType): diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index 2697e975e..c11a5c914 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -83,7 +83,7 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - if not workflow["needs_extended"]: workflow["needs_extended"] = True - list_names = ( + list_values = ( ["tags", "links"] + [x["option"] for x in needs_config.extra_links] + [f"{x['option']}_back" for x in needs_config.extra_links] @@ -93,13 +93,12 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - all_needs = data.get_or_create_needs() for current_needextend in data.get_or_create_extends().values(): - # Check if filter is just a need-id. - # In this case create the needed filter string need_filter = current_needextend["filter"] if need_filter in all_needs: + # a single known ID found_needs = [all_needs[need_filter]] - # If it looks like a need id, but we haven't found one, raise an exception elif need_filter is not None and re.fullmatch(needs_config.id_regex, need_filter): + # an unknown ID error = f"Provided id {need_filter} for needextend does not exist." if current_needextend["strict"]: raise NeedsInvalidFilter(error) @@ -107,6 +106,7 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - logger.info(error) continue else: + # a filter string try: found_needs = filter_needs(app, all_needs.values(), need_filter) except NeedsInvalidFilter as e: @@ -124,64 +124,65 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - if option.startswith("+"): option_name = option[1:] - # If we need to handle a list - if option_name in list_names: - for link in re.split(";|,", value): - # Remove whitespaces - link = link.strip() - if link not in need[option_name]: - need[option_name].append(link) + if option_name in list_values: + items = [i.strip() for i in re.split(";|,", value)] - # If we manipulate links, we need to set all the reference in the target need - # under e.g. links_back + for item in items: + if item not in need[option_name]: + need[option_name].append(item) + + # If we add links, + # we need to add all the corresponding back links if option_name in link_names: - for ref_need in re.split(";|,", value): - # Remove whitespaces - ref_need = ref_need.strip() - if found_need["id"] not in all_needs[ref_need][f"{option_name}_back"]: + for ref_need in items: + if ( + ref_need in all_needs + and found_need["id"] not in all_needs[ref_need][f"{option_name}_back"] + ): all_needs[ref_need][f"{option_name}_back"] += [found_need["id"]] - # else it must be a normal string else: - # If content is already stored, we need to add some whitespace if need[option_name]: + # If content is already stored, we need to add some whitespace need[option_name] += " " need[option_name] += value + elif option.startswith("-"): option_name = option[1:] - if option_name in list_names: - old_content = need[option_name] # Save it, as it may be need to identify referenced needs + if option_name in list_values: + old_content = need[option_name] need[option_name] = [] - # If we manipulate links, we need to delete the reference in the target need as well + # If we remove links, + # we need to remove all corresponding back links if option_name in link_names: - for ref_need in old_content: # There may be several links - all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"]) + for ref_need in old_content: + if ref_need in all_needs: + all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"]) else: need[option_name] = "" else: - if option in list_names: + if option in list_values: old_content = need[option].copy() - need[option] = [] - for link in re.split(";|,", value): - # Remove whitespaces - link = link.strip() - if link not in need[option]: - need[option].append(link) - # If add new links also as "link_s_back" to the referenced need. - if option in link_names: - # Remove old links - for ref_need in old_content: # There may be several links - all_needs[ref_need][f"{option}_back"].remove(found_need["id"]) + for item in [i.strip() for i in re.split(";|,", value)]: + if item not in need[option]: + need[option].append(item) - # Add new links - for ref_need in need[option]: # There may be several links - if found_need["id"] not in all_needs[ref_need][f"{option}_back"]: + # If we change links, + # we need to modify all corresponding back links + if option in link_names: + for ref_need in old_content: + if ref_need in all_needs: + all_needs[ref_need][f"{option}_back"].remove(found_need["id"]) + for ref_need in need[option]: + if ( + ref_need in all_needs + and found_need["id"] not in all_needs[ref_need][f"{option}_back"] + ): all_needs[ref_need][f"{option}_back"] += [found_need["id"]] - else: need[option] = value From cec6a1e3972d7ae5b9460682dff7effb974b5f2c Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 25 Sep 2023 16:12:35 -0300 Subject: [PATCH 2/8] Update needextend.py --- sphinx_needs/directives/needextend.py | 58 +++++++++++---------------- 1 file changed, 23 insertions(+), 35 deletions(-) diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index c11a5c914..6cf139ed0 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -124,23 +124,20 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - if option.startswith("+"): option_name = option[1:] - if option_name in list_values: - items = [i.strip() for i in re.split(";|,", value)] - - for item in items: + if option_name in link_names: + # If we add links, check they exist, + # and add the corresponding back link + for ref_need in [i.strip() for i in re.split(";|,", value)]: + if ref_need not in all_needs: + continue + if ref_need not in need[option_name]: + need[option_name].append(ref_need) + if found_need["id"] not in all_needs[ref_need][f"{option_name}_back"]: + all_needs[ref_need][f"{option_name}_back"] += [found_need["id"]] + elif option_name in list_values: + for item in [i.strip() for i in re.split(";|,", value)]: if item not in need[option_name]: need[option_name].append(item) - - # If we add links, - # we need to add all the corresponding back links - if option_name in link_names: - for ref_need in items: - if ( - ref_need in all_needs - and found_need["id"] not in all_needs[ref_need][f"{option_name}_back"] - ): - all_needs[ref_need][f"{option_name}_back"] += [found_need["id"]] - else: if need[option_name]: # If content is already stored, we need to add some whitespace @@ -153,8 +150,7 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - old_content = need[option_name] need[option_name] = [] - # If we remove links, - # we need to remove all corresponding back links + # If we remove links, then remove all corresponding back links if option_name in link_names: for ref_need in old_content: if ref_need in all_needs: @@ -163,26 +159,18 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - else: need[option_name] = "" else: - if option in list_values: - old_content = need[option].copy() - need[option] = [] - - for item in [i.strip() for i in re.split(";|,", value)]: - if item not in need[option]: - need[option].append(item) - + if option in link_names: # If we change links, # we need to modify all corresponding back links - if option in link_names: - for ref_need in old_content: - if ref_need in all_needs: - all_needs[ref_need][f"{option}_back"].remove(found_need["id"]) - for ref_need in need[option]: - if ( - ref_need in all_needs - and found_need["id"] not in all_needs[ref_need][f"{option}_back"] - ): - all_needs[ref_need][f"{option}_back"] += [found_need["id"]] + old_links = [i for i in need[option] if i in all_needs] + need[option] = [i.strip() for i in re.split(";|,", value) if i.strip() in all_needs] + for ref_need in old_links: + all_needs[ref_need][f"{option}_back"].remove(found_need["id"]) + for ref_need in need[option]: + if found_need["id"] not in all_needs[ref_need][f"{option}_back"]: + all_needs[ref_need][f"{option}_back"] += [found_need["id"]] + elif option in list_values: + need[option] = [i.strip() for i in re.split(";|,", value)] else: need[option] = value From b19d6e7ee3c6b671a6547e7efb83f8cef6a801ca Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 25 Sep 2023 16:14:14 -0300 Subject: [PATCH 3/8] Update needextend.py --- sphinx_needs/directives/needextend.py | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index 6cf139ed0..a25678e68 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -125,8 +125,7 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - option_name = option[1:] if option_name in link_names: - # If we add links, check they exist, - # and add the corresponding back link + # If we add links, then add all corresponding back links for ref_need in [i.strip() for i in re.split(";|,", value)]: if ref_need not in all_needs: continue @@ -160,8 +159,7 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - need[option_name] = "" else: if option in link_names: - # If we change links, - # we need to modify all corresponding back links + # If we change links, then modify all corresponding back links old_links = [i for i in need[option] if i in all_needs] need[option] = [i.strip() for i in re.split(";|,", value) if i.strip() in all_needs] for ref_need in old_links: From 977b2ddc73b837ac8c85dabfce29efbb5a09185f Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 25 Sep 2023 16:17:04 -0300 Subject: [PATCH 4/8] Update needextend.py --- sphinx_needs/directives/needextend.py | 10 ++++------ 1 file changed, 4 insertions(+), 6 deletions(-) diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index a25678e68..6975f8943 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -146,14 +146,12 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - elif option.startswith("-"): option_name = option[1:] if option_name in list_values: - old_content = need[option_name] - need[option_name] = [] - # If we remove links, then remove all corresponding back links + old_links = [i for i in need[option] if i in all_needs] + need[option_name] = [] if option_name in link_names: - for ref_need in old_content: - if ref_need in all_needs: - all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"]) + for ref_need in old_links: + all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"]) else: need[option_name] = "" From ded49d18ddbe2cd02cee6b08bf063a254a18cb3e Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 25 Sep 2023 16:19:57 -0300 Subject: [PATCH 5/8] Update needextend.py --- sphinx_needs/directives/needextend.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index 6975f8943..7f7e29d16 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -145,14 +145,14 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - elif option.startswith("-"): option_name = option[1:] - if option_name in list_values: + if option_name in link_names: # If we remove links, then remove all corresponding back links old_links = [i for i in need[option] if i in all_needs] need[option_name] = [] - if option_name in link_names: - for ref_need in old_links: - all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"]) - + for ref_need in old_links: + all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"]) + if option_name in list_values: + need[option_name] = [] else: need[option_name] = "" else: From 6e130292c6c819b79f81c02bff1a86e4ddb52eaa Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 25 Sep 2023 16:22:42 -0300 Subject: [PATCH 6/8] Update needextend.py --- sphinx_needs/directives/needextend.py | 11 ++++------- 1 file changed, 4 insertions(+), 7 deletions(-) diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index 7f7e29d16..b0841fbcb 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -123,7 +123,6 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - for option, value in current_needextend["modifications"].items(): if option.startswith("+"): option_name = option[1:] - if option_name in link_names: # If we add links, then add all corresponding back links for ref_need in [i.strip() for i in re.split(";|,", value)]: @@ -147,10 +146,9 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - option_name = option[1:] if option_name in link_names: # If we remove links, then remove all corresponding back links - old_links = [i for i in need[option] if i in all_needs] - need[option_name] = [] - for ref_need in old_links: + for ref_need in (i for i in need[option] if i in all_needs): all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"]) + need[option_name] = [] if option_name in list_values: need[option_name] = [] else: @@ -158,10 +156,9 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - else: if option in link_names: # If we change links, then modify all corresponding back links - old_links = [i for i in need[option] if i in all_needs] - need[option] = [i.strip() for i in re.split(";|,", value) if i.strip() in all_needs] - for ref_need in old_links: + for ref_need in (i for i in need[option] if i in all_needs): all_needs[ref_need][f"{option}_back"].remove(found_need["id"]) + need[option] = [i.strip() for i in re.split(";|,", value) if i.strip() in all_needs] for ref_need in need[option]: if found_need["id"] not in all_needs[ref_need][f"{option}_back"]: all_needs[ref_need][f"{option}_back"] += [found_need["id"]] From bc67def799f70b8d046c5a1e798672085dd82aa1 Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Mon, 25 Sep 2023 16:24:03 -0300 Subject: [PATCH 7/8] Update needextend.py --- sphinx_needs/directives/needextend.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index b0841fbcb..1911c29d0 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -146,7 +146,7 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - option_name = option[1:] if option_name in link_names: # If we remove links, then remove all corresponding back links - for ref_need in (i for i in need[option] if i in all_needs): + for ref_need in (i for i in need[option_name] if i in all_needs): all_needs[ref_need][f"{option_name}_back"].remove(found_need["id"]) need[option_name] = [] if option_name in list_values: From 43eb3da7f6335aab838899980556b25b79ec438b Mon Sep 17 00:00:00 2001 From: Chris Sewell Date: Tue, 26 Sep 2023 02:29:11 -0300 Subject: [PATCH 8/8] Add warning for non-existent links --- sphinx_needs/directives/needextend.py | 16 +- tests/__snapshots__/test_needextend.ambr | 455 +++++++++++++++++++++++ tests/doc_test/doc_needextend/conf.py | 1 + tests/test_needextend.py | 9 +- 4 files changed, 479 insertions(+), 2 deletions(-) create mode 100644 tests/__snapshots__/test_needextend.ambr diff --git a/sphinx_needs/directives/needextend.py b/sphinx_needs/directives/needextend.py index 1911c29d0..2b10fddb3 100644 --- a/sphinx_needs/directives/needextend.py +++ b/sphinx_needs/directives/needextend.py @@ -127,6 +127,11 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - # If we add links, then add all corresponding back links for ref_need in [i.strip() for i in re.split(";|,", value)]: if ref_need not in all_needs: + logger.warning( + f"Provided link id {ref_need} for needextend does not exist. [needs]", + type="needs", + location=(current_needextend["docname"], current_needextend["lineno"]), + ) continue if ref_need not in need[option_name]: need[option_name].append(ref_need) @@ -158,7 +163,16 @@ def process_needextend(app: Sphinx, doctree: nodes.document, fromdocname: str) - # If we change links, then modify all corresponding back links for ref_need in (i for i in need[option] if i in all_needs): all_needs[ref_need][f"{option}_back"].remove(found_need["id"]) - need[option] = [i.strip() for i in re.split(";|,", value) if i.strip() in all_needs] + need[option] = [] + for ref_need in [i.strip() for i in re.split(";|,", value)]: + if ref_need not in all_needs: + logger.warning( + f"Provided link id {ref_need} for needextend does not exist. [needs]", + type="needs", + location=(current_needextend["docname"], current_needextend["lineno"]), + ) + continue + need[option].append(ref_need) for ref_need in need[option]: if found_need["id"] not in all_needs[ref_need][f"{option}_back"]: all_needs[ref_need][f"{option}_back"] += [found_need["id"]] diff --git a/tests/__snapshots__/test_needextend.ambr b/tests/__snapshots__/test_needextend.ambr new file mode 100644 index 000000000..ca3a866b5 --- /dev/null +++ b/tests/__snapshots__/test_needextend.ambr @@ -0,0 +1,455 @@ +# serializer version: 1 +# name: test_doc_needextend_html[test_app0] + dict({ + 'current_version': '', + 'project': 'Python', + 'versions': dict({ + '': dict({ + 'filters': dict({ + }), + 'filters_amount': 0, + 'needs': dict({ + 'extend_test_003': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content_id': 'extend_test_003', + 'created_at': '', + 'delete': None, + 'description': ''' + Had no outgoing links. + Got an outgoing link ``extend_test_004``. + ''', + 'docname': 'index', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'needextend Example 3', + 'has_dead_links': '', + 'has_forbidden_dead_links': '', + 'hidden': '', + 'id': 'extend_test_003', + 'id_prefix': '', + 'is_external': False, + 'is_modified': True, + 'is_need': True, + 'is_part': False, + 'jinja_content': None, + 'layout': '', + 'links': list([ + 'extend_test_004', + ]), + 'max_amount': '', + 'max_content_lines': '', + 'modifications': 1, + 'params': '', + 'parent_need': '', + 'parent_needs': list([ + ]), + 'parent_needs_back': list([ + ]), + 'parts': dict({ + }), + 'post_template': None, + 'pre_template': None, + 'prefix': '', + 'query': '', + 'section_name': 'Need extend', + 'sections': list([ + 'Need extend', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': None, + 'style': None, + 'tags': list([ + ]), + 'target_id': 'extend_test_003', + 'template': None, + 'title': 'needextend Example 3', + 'type': 'story', + 'type_name': 'User Story', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + 'extend_test_004': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content_id': 'extend_test_004', + 'created_at': '', + 'delete': None, + 'description': ''' + Had no links. + Got an incoming links ``extend_test_003`` and ``extend_test_006``. + ''', + 'docname': 'index', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'needextend Example 4', + 'has_dead_links': '', + 'has_forbidden_dead_links': '', + 'hidden': '', + 'id': 'extend_test_004', + 'id_prefix': '', + 'is_external': False, + 'is_modified': False, + 'is_need': True, + 'is_part': False, + 'jinja_content': None, + 'layout': '', + 'links': list([ + ]), + 'max_amount': '', + 'max_content_lines': '', + 'modifications': 0, + 'params': '', + 'parent_need': '', + 'parent_needs': list([ + ]), + 'parent_needs_back': list([ + ]), + 'parts': dict({ + }), + 'post_template': None, + 'pre_template': None, + 'prefix': '', + 'query': '', + 'section_name': 'Need extend', + 'sections': list([ + 'Need extend', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': None, + 'style': None, + 'tags': list([ + ]), + 'target_id': 'extend_test_004', + 'template': None, + 'title': 'needextend Example 4', + 'type': 'story', + 'type_name': 'User Story', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + 'extend_test_005': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content_id': 'extend_test_005', + 'created_at': '', + 'delete': None, + 'description': ''' + Had the two links: ``extend_test_003`` and ``extend_test_004``. + Both got deleted. + ''', + 'docname': 'index', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'needextend Example 5', + 'has_dead_links': '', + 'has_forbidden_dead_links': '', + 'hidden': '', + 'id': 'extend_test_005', + 'id_prefix': '', + 'is_external': False, + 'is_modified': True, + 'is_need': True, + 'is_part': False, + 'jinja_content': None, + 'layout': '', + 'links': list([ + ]), + 'max_amount': '', + 'max_content_lines': '', + 'modifications': 1, + 'params': '', + 'parent_need': '', + 'parent_needs': list([ + ]), + 'parent_needs_back': list([ + ]), + 'parts': dict({ + }), + 'post_template': None, + 'pre_template': None, + 'prefix': '', + 'query': '', + 'section_name': 'Need extend', + 'sections': list([ + 'Need extend', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': None, + 'style': None, + 'tags': list([ + ]), + 'target_id': 'extend_test_005', + 'template': None, + 'title': 'needextend Example 5', + 'type': 'story', + 'type_name': 'User Story', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + 'extend_test_006': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content_id': 'extend_test_006', + 'created_at': '', + 'delete': None, + 'description': 'Had the link ``extend_test_003``, got another one ``extend_test_004``.', + 'docname': 'index', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'needextend Example 6', + 'has_dead_links': '', + 'has_forbidden_dead_links': '', + 'hidden': '', + 'id': 'extend_test_006', + 'id_prefix': '', + 'is_external': False, + 'is_modified': True, + 'is_need': True, + 'is_part': False, + 'jinja_content': None, + 'layout': '', + 'links': list([ + 'extend_test_003', + 'extend_test_004', + ]), + 'max_amount': '', + 'max_content_lines': '', + 'modifications': 2, + 'params': '', + 'parent_need': '', + 'parent_needs': list([ + ]), + 'parent_needs_back': list([ + ]), + 'parts': dict({ + }), + 'post_template': None, + 'pre_template': None, + 'prefix': '', + 'query': '', + 'section_name': 'Need extend', + 'sections': list([ + 'Need extend', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': None, + 'style': None, + 'tags': list([ + ]), + 'target_id': 'extend_test_006', + 'template': None, + 'title': 'needextend Example 6', + 'type': 'story', + 'type_name': 'User Story', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + 'extend_test_007': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content_id': 'extend_test_007', + 'created_at': '', + 'delete': None, + 'description': '', + 'docname': 'index', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'needextend Example 7', + 'has_dead_links': '', + 'has_forbidden_dead_links': '', + 'hidden': '', + 'id': 'extend_test_007', + 'id_prefix': '', + 'is_external': False, + 'is_modified': True, + 'is_need': True, + 'is_part': False, + 'jinja_content': None, + 'layout': '', + 'links': list([ + 'extend_test_003', + 'extend_test_004', + 'extend_test_005', + ]), + 'max_amount': '', + 'max_content_lines': '', + 'modifications': 1, + 'params': '', + 'parent_need': '', + 'parent_needs': list([ + ]), + 'parent_needs_back': list([ + ]), + 'parts': dict({ + }), + 'post_template': None, + 'pre_template': None, + 'prefix': '', + 'query': '', + 'section_name': 'Need extend', + 'sections': list([ + 'Need extend', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': None, + 'style': None, + 'tags': list([ + ]), + 'target_id': 'extend_test_007', + 'template': None, + 'title': 'needextend Example 7', + 'type': 'story', + 'type_name': 'User Story', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + 'extend_test_page': dict({ + 'arch': dict({ + }), + 'avatar': '', + 'closed_at': '', + 'completion': '', + 'constraints': list([ + ]), + 'constraints_passed': True, + 'constraints_results': dict({ + }), + 'content_id': 'extend_test_page', + 'created_at': '', + 'delete': None, + 'description': '', + 'docname': 'page_1', + 'doctype': '.rst', + 'duration': '', + 'external_css': 'external_link', + 'external_url': None, + 'full_title': 'needextend different pages test', + 'has_dead_links': '', + 'has_forbidden_dead_links': '', + 'hidden': '', + 'id': 'extend_test_page', + 'id_prefix': '', + 'is_external': False, + 'is_modified': True, + 'is_need': True, + 'is_part': False, + 'jinja_content': None, + 'layout': '', + 'links': list([ + ]), + 'max_amount': '', + 'max_content_lines': '', + 'modifications': 2, + 'params': '', + 'parent_need': '', + 'parent_needs': list([ + ]), + 'parent_needs_back': list([ + ]), + 'parts': dict({ + }), + 'post_template': None, + 'pre_template': None, + 'prefix': '', + 'query': '', + 'section_name': 'need objects', + 'sections': list([ + 'need objects', + ]), + 'service': '', + 'signature': '', + 'specific': '', + 'status': 'closed', + 'style': None, + 'tags': list([ + 'tag_1', + 'new_tag', + 'another_tag', + ]), + 'target_id': 'extend_test_page', + 'template': None, + 'title': 'needextend different pages test', + 'type': 'story', + 'type_name': 'User Story', + 'updated_at': '', + 'url': '', + 'url_postfix': '', + 'user': '', + }), + }), + 'needs_amount': 6, + }), + }), + }) +# --- diff --git a/tests/doc_test/doc_needextend/conf.py b/tests/doc_test/doc_needextend/conf.py index 4e7116d36..7f111502f 100644 --- a/tests/doc_test/doc_needextend/conf.py +++ b/tests/doc_test/doc_needextend/conf.py @@ -2,6 +2,7 @@ extensions = ["sphinx_needs"] +needs_build_json = True needs_id_regex = "^[A-Za-z0-9_]*" needs_types = [ {"directive": "story", "title": "User Story", "prefix": "US_", "color": "#BFD8D2", "style": "node"}, diff --git a/tests/test_needextend.py b/tests/test_needextend.py index 19f9ae54d..0751f87d0 100644 --- a/tests/test_needextend.py +++ b/tests/test_needextend.py @@ -1,13 +1,20 @@ +import json import sys from pathlib import Path import pytest +from sphinx.application import Sphinx +from syrupy.filters import props @pytest.mark.parametrize("test_app", [{"buildername": "html", "srcdir": "doc_test/doc_needextend"}], indirect=True) -def test_doc_needextend_html(test_app): +def test_doc_needextend_html(test_app: Sphinx, snapshot): app = test_app app.build() + + needs_data = json.loads(Path(app.outdir, "needs.json").read_text()) + assert needs_data == snapshot(exclude=props("created")) + index_html = Path(app.outdir, "index.html").read_text() assert "extend_test_003" in index_html