diff --git a/iXBRLViewerPlugin/iXBRLViewer.py b/iXBRLViewerPlugin/iXBRLViewer.py index 80afd036e..917e8b2a2 100644 --- a/iXBRLViewerPlugin/iXBRLViewer.py +++ b/iXBRLViewerPlugin/iXBRLViewer.py @@ -87,7 +87,7 @@ def __init__(self, reports: List[ModelXbrl], basenameSuffix: str = ''): self.roleMap = NamespaceMap() self.reports = reports self.taxonomyData = { - "reports": [], + "sourceReports": [], "features": [], "languages": {}, } @@ -145,9 +145,13 @@ def addLanguage(self, langCode): if langCode not in self.taxonomyData["languages"]: self.taxonomyData["languages"][langCode] = self.makeLanguageName(langCode) + @property + def currentSourceReportData(self): + return self.taxonomyData["sourceReports"][-1] + @property def currentReportData(self): - return self.taxonomyData["reports"][-1] + return self.currentSourceReportData["targetReports"][-1] def addELR(self, report: ModelXbrl, elr): prefix = self.roleMap.getPrefix(elr) @@ -379,9 +383,13 @@ def getStubDocument(self): return etree.parse(fin) def addReport(self): - self.taxonomyData["reports"].append({ - "concepts": {}, - "facts": {}, + self.taxonomyData["sourceReports"].append({ + "targetReports": [ + { + "concepts": {}, + "facts": {}, + } + ] }) def createViewer(self, scriptUrl: str = DEFAULT_VIEWER_PATH, useStubViewer: bool = False, showValidations: bool = True, packageDownloadURL: str = None) -> Optional[iXBRLViewer]: @@ -479,7 +487,7 @@ def createViewer(self, scriptUrl: str = DEFAULT_VIEWER_PATH, useStubViewer: bool } if docSetFiles is not None: - self.currentReportData["docSetFiles"] = list(urllib.parse.quote(f) for f in docSetFiles) + self.currentSourceReportData["docSetFiles"] = list(urllib.parse.quote(f) for f in docSetFiles) self.taxonomyData["prefixes"] = self.nsmap.prefixmap self.taxonomyData["roles"] = self.roleMap.prefixmap diff --git a/iXBRLViewerPlugin/viewer/src/js/reportset.js b/iXBRLViewerPlugin/viewer/src/js/reportset.js index 28cd4875f..229e1157c 100644 --- a/iXBRLViewerPlugin/viewer/src/js/reportset.js +++ b/iXBRLViewerPlugin/viewer/src/js/reportset.js @@ -38,34 +38,36 @@ export class ReportSet { fnorder.sort((a,b) => this._ixNodeMap[a].docOrderindex - this._ixNodeMap[b].docOrderindex); // Create Fact objects for all facts. - for (const [reportIndex, reportData] of this.reportsData().entries()) { - const report = new XBRLReport(this, reportData); - this.reports.push(report); - for (const [id, factData] of Object.entries(reportData.facts)) { - const vuid = viewerUniqueId(reportIndex, id); - this._items[vuid] = new Fact(report, vuid, factData); - } + for (const [reportIndex, sourceReport] of (this._data.sourceReports ?? [ { "targetReports": [ this._data ] } ]).entries()) { + for (const reportData of sourceReport.targetReports) { + const report = new XBRLReport(this, reportData); + this.reports.push(report); + for (const [id, factData] of Object.entries(reportData.facts)) { + const vuid = viewerUniqueId(reportIndex, id); + this._items[vuid] = new Fact(report, vuid, factData); + } - // Now resolve footnote references, creating footnote objects for "normal" - // footnotes, and finding Fact objects for fact->fact footnotes. - // - // Associate source facts with target footnote/facts to allow two way - // navigation. - for (const [id, factData] of Object.entries(reportData.facts)) { - const vuid = viewerUniqueId(reportIndex, id); - const fact = this._items[vuid]; - const fns = factData.fn || []; - fns.forEach((fnid) => { - const fnvuid = viewerUniqueId(reportIndex, fnid); - var fn = this._items[fnvuid]; - if (fn === undefined) { - fn = new Footnote(fact.report, fnvuid, "Footnote " + (fnorder.indexOf(fnvuid) + 1)); - this._items[fnvuid] = fn; - } - // Associate fact with footnote - fn.addLinkedFact(fact); - fact.addFootnote(fn); - }); + // Now resolve footnote references, creating footnote objects for "normal" + // footnotes, and finding Fact objects for fact->fact footnotes. + // + // Associate source facts with target footnote/facts to allow two way + // navigation. + for (const [id, factData] of Object.entries(reportData.facts)) { + const vuid = viewerUniqueId(reportIndex, id); + const fact = this._items[vuid]; + const fns = factData.fn || []; + fns.forEach((fnid) => { + const fnvuid = viewerUniqueId(reportIndex, fnid); + var fn = this._items[fnvuid]; + if (fn === undefined) { + fn = new Footnote(fact.report, fnvuid, "Footnote " + (fnorder.indexOf(fnvuid) + 1)); + this._items[fnvuid] = fn; + } + // Associate fact with footnote + fn.addLinkedFact(fact); + fact.addFootnote(fn); + }); + } } } } @@ -181,7 +183,7 @@ export class ReportSet { } reportsData() { - return this._data.reports ?? [ this._data ]; + return this._data.sourceReports?.flatMap(sr => sr.targetReports) ?? [ this._data ]; } /** @@ -193,7 +195,8 @@ export class ReportSet { * @return {List} A list of objects describing each file */ reportFiles() { - return this.reportsData().map((x, n) => (x.docSetFiles ?? []).map(file => ({ index: n, file: file }))).flat(); + const sourceReports = this._data.sourceReports ?? [ this._data ]; + return sourceReports.map((x, n) => (x.docSetFiles ?? []).map(file => ({ index: n, file: file }))).flat(); } /** diff --git a/iXBRLViewerPlugin/viewer/src/js/reportset.test.js b/iXBRLViewerPlugin/viewer/src/js/reportset.test.js index 97d5b09f5..d2415651d 100644 --- a/iXBRLViewerPlugin/viewer/src/js/reportset.test.js +++ b/iXBRLViewerPlugin/viewer/src/js/reportset.test.js @@ -36,83 +36,91 @@ function multiReportTestData(withAnchoring) { "role3": "https://www.example.com/role3", "role4": "https://www.example.com/role4" }, - "reports": [ - { - "roleDefs": { - "role1": { "en": "Role 1 Label" }, - "role2": { "en": null }, - "role3": {} - }, - "concepts": { - "eg:Concept1": { - "labels": { - "std": { - "en": "English label" - } - } - }, - "eg:Concept2": { - "labels": { - "std": { - "en": "English label for concept two", - "en-us": "English (US) label for concept two", + "sourceReports": [ + { + "targetReports": [ + { + "roleDefs": { + "role1": { "en": "Role 1 Label" }, + "role2": { "en": null }, + "role3": {} + }, + "concepts": { + "eg:Concept1": { + "labels": { + "std": { + "en": "English label" + } + } + }, + "eg:Concept2": { + "labels": { + "std": { + "en": "English label for concept two", + "en-us": "English (US) label for concept two", + } + } + }, + "eg:Concept3": { + "labels": { + "std": { + "en": "Concept three" + } + } } + }, + + "facts": { + ...createNumericFact("f1", "eg:Concept1", "iso2417:USD", "2018-01-01/2019-01-01", 1000, -3), + ...createNumericFact("f2", "eg:Concept2", "iso2417:USD", "2018-01-01/2019-01-01", 1000, -3), } }, - "eg:Concept3": { - "labels": { - "std": { - "en": "Concept three" - } - } - } - }, - - "facts": { - ...createNumericFact("f1", "eg:Concept1", "iso2417:USD", "2018-01-01/2019-01-01", 1000, -3), - ...createNumericFact("f2", "eg:Concept2", "iso2417:USD", "2018-01-01/2019-01-01", 1000, -3), - } + ] }, { - "roleDefs": { - }, - "concepts": { - "eg:Concept1": { - "labels": { - "std": { - "en": "English label" + "targetReports": [ + { + "roleDefs": { + }, + "concepts": { + "eg:Concept1": { + "labels": { + "std": { + "en": "English label" + } + } + }, + "eg:Concept2": { + "labels": { + "std": { + "en": "Report 2 English label for concept two", + } + } + }, + "eg:Concept3": { + "labels": { + "std": { + "en-gb": "Concept three" + } + } } - } - }, - "eg:Concept2": { - "labels": { - "std": { - "en": "Report 2 English label for concept two", - } - } - }, - "eg:Concept3": { - "labels": { - "std": { - "en-gb": "Concept three" - } - } - } - }, + }, - "facts": { - ...createNumericFact("f1", "eg:Concept1", "iso2417:USD", "2018-01-01/2019-01-01", 2000, -3), - }, + "facts": { + ...createNumericFact("f1", "eg:Concept1", "iso2417:USD", "2018-01-01/2019-01-01", 2000, -3), + }, - "rels": { - ...anchoringRelationShips(withAnchoring) - } + "rels": { + ...anchoringRelationShips(withAnchoring) + } + } + ] } ] }; } -// Legacy report data format - no "reports" array +// Legacy report data format - no "sourceReports" array function singleReportTestData() { return { "features": [], @@ -254,16 +262,16 @@ describe("Multi report - anchoring", () => { describe("Multi report - doc set files", () => { test("Not overlapping", () => { const data = multiReportTestData(false); - data.reports[0].docSetFiles = ["a.html", "b.html"]; - data.reports[1].docSetFiles = ["c.html", "d.html"]; + data.sourceReports[0].docSetFiles = ["a.html", "b.html"]; + data.sourceReports[1].docSetFiles = ["c.html", "d.html"]; const testReportSet = new ReportSet(data); testReportSet._initialize(); expect(testReportSet.reportFiles()).toStrictEqual([{"index": 0, "file": "a.html"}, {"index": 0, "file": "b.html"}, {"index": 1, "file": "c.html"}, {"index": 1, "file": "d.html"}]); }); test("Repeated document set", () => { const data = multiReportTestData(false); - data.reports[0].docSetFiles = ["a.html", "b.html"]; - data.reports[1].docSetFiles = ["a.html", "b.html"]; + data.sourceReports[0].docSetFiles = ["a.html", "b.html"]; + data.sourceReports[1].docSetFiles = ["a.html", "b.html"]; const testReportSet = new ReportSet(data); testReportSet._initialize(); expect(testReportSet.reportFiles()).toStrictEqual([{"index": 0, "file": "a.html"}, {"index": 0, "file": "b.html"}, {"index": 1, "file": "a.html"}, {"index": 1, "file": "b.html"}]); diff --git a/iXBRLViewerPlugin/viewer/src/js/util.js b/iXBRLViewerPlugin/viewer/src/js/util.js index f99264de1..e39760b95 100644 --- a/iXBRLViewerPlugin/viewer/src/js/util.js +++ b/iXBRLViewerPlugin/viewer/src/js/util.js @@ -166,13 +166,13 @@ export function isTransparent(rgba) { * in order to generate an ID that is guaranteed to be unique within the * viewer. This is already within an iXBRL Document Set, but not across * separate iXBRL documents or document sets. - * @param {Integer} reportIndex Index of the report document containing the fact + * @param {Integer} sourceReportIndex - index of the report document containing the fact * @return {String} a viewer unique ID */ -export function viewerUniqueId(reportIndex, id) { +export function viewerUniqueId(sourceReportIndex, id) { if (id === null) { return null; } - return reportIndex.toString() + "-" + id; + return sourceReportIndex.toString() + "-" + id; } diff --git a/tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py b/tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py index 7aa72defd..1b129048b 100644 --- a/tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py +++ b/tests/unit_tests/iXBRLViewerPlugin/test_iXBRLViewer.py @@ -321,20 +321,26 @@ def info_effect(info, msg): self.modelDocument = Mock( xmlDocument=etree.ElementTree(root), - filepath='c.xml' + filepath='a.html', + referencesDocument={ + "xmlDocument":etree.ElementTree(root), + "filepath":'a.html', + "objectIndex":0, + "type":Type.INLINEXBRL, + } ) self.modelDocumentInlineSet = Mock( referencesDocument={ Mock( xmlDocument=etree.ElementTree(root), - filepath='a.xml', + filepath='a.html', objectIndex=0, type=Type.INLINEXBRL, ): [], Mock( xmlDocument=etree.ElementTree(root), - filepath='b.xml', + filepath='b.html', objectIndex=1, type=Type.INLINEXBRL, ): [], @@ -434,7 +440,6 @@ def urlDocEntry(path, docType, linkQName=None): typed_dimension_domain_concept.modelXbrl = self.modelXbrl_1 member_concept.modelXbrl = self.modelXbrl_1 - self.builder_1 = IXBRLViewerBuilder([self.modelXbrl_1]) self.builder_2 = IXBRLViewerBuilder([self.modelXbrl_1]) self.builder_3 = IXBRLViewerBuilder([self.modelXbrl_2]) @@ -443,9 +448,10 @@ def urlDocEntry(path, docType, linkQName=None): @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label') @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference') def test_addConcept_simple_case(self): - self.builder_1.addReport() - self.builder_1.addConcept(self.modelXbrl_1, self.cash_concept) - self.assertTrue(self.builder_1.taxonomyData["reports"][0].get('concepts').get('us-gaap:Cash')) + builder = IXBRLViewerBuilder([self.modelXbrl_1]) + builder.addReport() + builder.addConcept(self.modelXbrl_1, self.cash_concept) + self.assertTrue(builder.taxonomyData["sourceReports"][0]["targetReports"][0].get('concepts').get('us-gaap:Cash')) @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child') @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item') @@ -458,9 +464,10 @@ def test_getRelationships_simple_case(self): @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child') @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item') def test_getRelationships_returns_a_rel(self): - self.builder_1.addReport() - result = self.builder_1.getRelationships(self.modelXbrl_1) - roleMap = self.builder_1.roleMap + builder = IXBRLViewerBuilder([self.modelXbrl_1]) + builder.addReport() + result = builder.getRelationships(self.modelXbrl_1) + roleMap = builder.roleMap siPrefix = roleMap.getPrefix('http://www.xbrl.org/2003/arcrole/summation-item') self.assertTrue(result.get(siPrefix).get(roleMap.getPrefix('ELR')).get('us-gaap:from_concept')) @@ -469,20 +476,22 @@ def test_addELR_no_definition(self): Adding an ELR with no definition should result in no entry in the roleDefs map """ elr = "http://example.com/unknownELR" - self.builder_1.addReport() - self.builder_1.addELR(self.modelXbrl_1, elr) - elrPrefix = self.builder_1.roleMap.getPrefix(elr) - self.assertEqual(self.builder_1.currentReportData.get('roleDefs').get(elrPrefix), None) + builder = IXBRLViewerBuilder([self.modelXbrl_1]) + builder.addReport() + builder.addELR(self.modelXbrl_1, elr) + elrPrefix = builder.roleMap.getPrefix(elr) + self.assertEqual(builder.currentReportData.get('roleDefs').get(elrPrefix), None) def test_addELR_with_definition(self): """ Adding an ELR with a definition should result in an "en" label with the definition as its value. """ elr = "ELR" - self.builder_1.addReport() - self.builder_1.addELR(self.modelXbrl_1, elr) - elrPrefix = self.builder_1.roleMap.getPrefix(elr) - self.assertEqual(self.builder_1.currentReportData.get('roleDefs').get(elrPrefix).get("en"), "ELR Label") + builder = IXBRLViewerBuilder([self.modelXbrl_1]) + builder.addReport() + builder.addELR(self.modelXbrl_1, elr) + elrPrefix = builder.roleMap.getPrefix(elr) + self.assertEqual(builder.currentReportData.get('roleDefs').get(elrPrefix).get("en"), "ELR Label") @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label') @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference') @@ -493,7 +502,8 @@ def test_addELR_with_definition(self): @patch('arelle.XbrlConst.documentationLabel', 'http://www.xbrl.org/2003/role/documentation') def test_createViewerWithValidation(self): js_uri = 'ixbrlviewer.js' - result = self.builder_1.createViewer(js_uri) + builder = IXBRLViewerBuilder([self.modelXbrl_1]) + result = builder.createViewer(js_uri) self.assertEqual(len(result.files),1) body = result.files[0].xmlDocument.getroot()[0] self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS') @@ -505,7 +515,7 @@ def test_createViewerWithValidation(self): jsdata = json.loads(body[2].text) errors = jsdata["validation"] self.assertEqual(errors, [{"sev": "ERROR", "msg": "Error message", "code": "code1" }]) - self.assertEqual(set(jsdata["reports"][0]["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"}) + self.assertEqual(set(jsdata["sourceReports"][0]["targetReports"][0]["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"}) @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label') @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference') @@ -516,8 +526,9 @@ def test_createViewerWithValidation(self): @patch('arelle.XbrlConst.dimensionDefault', 'http://xbrl.org/int/dim/arcrole/dimension-default') def test_createViewer(self): js_uri = 'ixbrlviewer.js' - result = self.builder_2.createViewer(js_uri, showValidations = False) - self.assertEqual(len(result.files),1) + builder = IXBRLViewerBuilder([self.modelXbrl_1]) + result = builder.createViewer(js_uri, showValidations = False) + self.assertEqual(len(result.files), 1) body = result.files[0].xmlDocument.getroot()[0] self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS') self.assertEqual(body[1].attrib.get('src'), js_uri) @@ -527,9 +538,9 @@ def test_createViewer(self): jsdata = json.loads(body[2].text) self.assertNotIn("validation", jsdata) - print(jsdata["reports"]) - self.assertEqual(set(jsdata["reports"][0]["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"}) - self.assertEqual(jsdata["reports"][0]["concepts"], { + reportData = jsdata["sourceReports"][0]["targetReports"][0] + self.assertEqual(set(reportData["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"}) + self.assertEqual(reportData["concepts"], { 'us-gaap:Cash': {'e': True, 't': True, 'labels': {}}, 'us-gaap:from_concept': {'e': True, 't': True, 'labels': {}}, 'us-gaap:to_concept': {'e': True, 't': True, 'labels': {}}, @@ -538,7 +549,7 @@ def test_createViewer(self): 'us-gaap:typed_dimension_domain': {'e': True, 't': True, 'labels': {}}, 'us-gaap:typed_dimension': {'d': 't', 'e': True, 't': True, 'labels': {}, 'td': 'us-gaap:typed_dimension_domain'}, }) - self.assertEqual(jsdata["reports"][0]["localDocs"], { + self.assertEqual(reportData["localDocs"], { 'local-inline.htm': ['inline'], 'local-schema.xsd': ['schema'], 'local-pres-linkbase.xml': ['presLinkbase'], @@ -549,6 +560,55 @@ def test_createViewer(self): 'local-unrecognized-linkbase.xml': ['unrecognizedLinkbase'], }) + # There's not actually a good reason for not including "docSetFiles" + # now, but it's what we do currently. + self.assertNotIn("docSetFiles", jsdata["sourceReports"][0]); + + @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label') + @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference') + @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child') + @patch('arelle.XbrlConst.summationItem', 'http://www.xbrl.org/2003/arcrole/summation-item') + @patch('arelle.XbrlConst.standardLabel', 'http://www.xbrl.org/2003/role/label') + @patch('arelle.XbrlConst.documentationLabel', 'http://www.xbrl.org/2003/role/documentation') + @patch('arelle.XbrlConst.dimensionDefault', 'http://xbrl.org/int/dim/arcrole/dimension-default') + def test_createStubViewer(self): + js_uri = 'ixbrlviewer.js' + builder = IXBRLViewerBuilder([self.modelXbrl_1]) + result = builder.createViewer(js_uri, showValidations = False, useStubViewer = True) + self.assertEqual(len(result.files), 2) + body = result.files[0].xmlDocument.getroot().find('{http://www.w3.org/1999/xhtml}body') + self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS') + self.assertEqual(body[1].attrib.get('src'), js_uri) + self.assertEqual(body[1].attrib.get('type'), 'text/javascript') + self.assertEqual(body[2].attrib.get('type'), 'application/x.ixbrl-viewer+json') + self.assertEqual(body[3].text, 'END IXBRL VIEWER EXTENSIONS') + + jsdata = json.loads(body[2].text) + self.assertNotIn("validation", jsdata) + reportData = jsdata["sourceReports"][0]["targetReports"][0] + self.assertEqual(set(reportData["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"}) + self.assertEqual(reportData["concepts"], { + 'us-gaap:Cash': {'e': True, 't': True, 'labels': {}}, + 'us-gaap:from_concept': {'e': True, 't': True, 'labels': {}}, + 'us-gaap:to_concept': {'e': True, 't': True, 'labels': {}}, + 'us-gaap:dimension': {'d': 'e', 'e': True, 't': True, 'labels': {}}, + 'us-gaap:member': {'e': True, 't': True, 'labels': {}}, + 'us-gaap:typed_dimension_domain': {'e': True, 't': True, 'labels': {}}, + 'us-gaap:typed_dimension': {'d': 't', 'e': True, 't': True, 'labels': {}, 'td': 'us-gaap:typed_dimension_domain'}, + }) + self.assertEqual(reportData["localDocs"], { + 'local-inline.htm': ['inline'], + 'local-schema.xsd': ['schema'], + 'local-pres-linkbase.xml': ['presLinkbase'], + 'local-calc-linkbase.xml': ['calcLinkbase'], + 'local-def-linkbase.xml': ['defLinkbase'], + 'local-label-linkbase.xml': ['labelLinkbase'], + 'local-ref-linkbase.xml': ['refLinkbase'], + 'local-unrecognized-linkbase.xml': ['unrecognizedLinkbase'], + }) + + self.assertEqual(jsdata["sourceReports"][0]["docSetFiles"], [ "a.html" ]); + @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label') @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference') @patch('arelle.XbrlConst.parentChild', 'http://www.xbrl.org/2003/arcrole/parent-child') @@ -569,7 +629,13 @@ def test_createViewer_docset(self): jsdata = json.loads(body[2].text) self.assertNotIn("validation", jsdata) - self.assertEqual(set(jsdata["reports"][0]["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"}) + reportData = jsdata["sourceReports"][0]["targetReports"][0] + self.assertEqual(set(reportData["facts"]), {"fact_id1", "fact_typed_dimension", "fact_dimension_missing_member"}) + + self.assertEqual(jsdata["sourceReports"][0]["docSetFiles"], [ + 'a.html', + 'b.html' + ]) @patch('arelle.XbrlConst.conceptLabel', 'http://www.xbrl.org/2003/arcrole/concept-label') @patch('arelle.XbrlConst.conceptReference', 'http://www.xbrl.org/2003/arcrole/concept-reference') @@ -580,7 +646,8 @@ def test_createViewer_docset(self): @patch('arelle.XbrlConst.documentationLabel', 'http://www.xbrl.org/2003/role/documentation') def test_createViewer_bad_path(self): js_uri = 'ixbrlviewer.js' - result = self.builder_3.createViewer(js_uri) + builder = IXBRLViewerBuilder([self.modelXbrl_2]) + result = builder.createViewer(js_uri) self.assertEqual(len(result.files),1) body = result.files[0].xmlDocument.getroot()[0] self.assertEqual(body[0].text, 'BEGIN IXBRL VIEWER EXTENSIONS') @@ -594,7 +661,7 @@ def test_createViewer_bad_path(self): self.assertEqual(body[3].text, 'END IXBRL VIEWER EXTENSIONS') jsdata = json.loads(body[2].text) - facts = jsdata["reports"][0]["facts"] + facts = jsdata["sourceReports"][0]["targetReports"][0]["facts"] self.assertEqual(facts.keys(), {"fact_id2", "fact_id3"}) self.assertEqual(facts["fact_id2"]["a"]["u"], "iso4217:USD") self.assertEqual(facts["fact_id3"]["a"]["u"], None) @@ -603,15 +670,17 @@ def test_enableFeature_valid(self): """ Enable a defined feature """ - self.builder_1.enableFeature('review') - self.assertEqual(self.builder_1.taxonomyData["features"], ['review']) + builder = IXBRLViewerBuilder([self.modelXbrl_1]) + builder.enableFeature('review') + self.assertEqual(builder.taxonomyData["features"], ['review']) def test_enableFeature_invalid(self): """ Attempt to enable an undefined feature """ + builder = IXBRLViewerBuilder([self.modelXbrl_1]) with self.assertRaisesRegex(AssertionError, rf'^Given feature name `unknown` does not match any defined features'): - self.builder_1.enableFeature('unknown') + builder.enableFeature('unknown') def test_xhtmlNamespaceHandling(self): # Check the prefix used for our inserted script tags