Files
Neon-Desk/lib/server/taxonomy/linkbase-parser.test.ts
2026-03-06 14:40:43 -05:00

64 lines
3.1 KiB
TypeScript

import { describe, expect, it } from 'bun:test';
import {
classifyStatementRole,
parseLabelLinkbase,
parsePresentationLinkbase
} from '@/lib/server/taxonomy/linkbase-parser';
const SAMPLE_LABEL_LINKBASE = `
<link:linkbase xmlns:link="http://www.xbrl.org/2003/linkbase"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:us-gaap="http://fasb.org/us-gaap/2024">
<link:labelLink xlink:type="extended">
<link:loc xlink:type="locator" xlink:label="loc_rev" xlink:href="test.xsd#us-gaap_Revenues" />
<link:label xlink:type="resource" xlink:label="lab_terse" xlink:role="http://www.xbrl.org/2003/role/terseLabel">Rev.</link:label>
<link:label xlink:type="resource" xlink:label="lab_label" xlink:role="http://www.xbrl.org/2003/role/label">Revenues</link:label>
<link:labelArc xlink:type="arc" xlink:from="loc_rev" xlink:to="lab_terse" />
<link:labelArc xlink:type="arc" xlink:from="loc_rev" xlink:to="lab_label" />
</link:labelLink>
</link:linkbase>
`;
const SAMPLE_PRESENTATION_LINKBASE = `
<link:linkbase xmlns:link="http://www.xbrl.org/2003/linkbase"
xmlns:xlink="http://www.w3.org/1999/xlink"
xmlns:us-gaap="http://fasb.org/us-gaap/2024">
<link:presentationLink xlink:type="extended" xlink:role="http://www.xbrl.org/2003/role/StatementOfOperations">
<link:loc xlink:type="locator" xlink:label="root" xlink:href="test.xsd#us-gaap_StatementLineItems" />
<link:loc xlink:type="locator" xlink:label="rev" xlink:href="test.xsd#us-gaap_Revenues" />
<link:loc xlink:type="locator" xlink:label="cogs" xlink:href="test.xsd#us-gaap_CostOfGoodsSold" />
<link:presentationArc xlink:type="arc" xlink:from="root" xlink:to="rev" order="1" />
<link:presentationArc xlink:type="arc" xlink:from="root" xlink:to="cogs" order="2" />
</link:presentationLink>
</link:linkbase>
`;
describe('linkbase parser', () => {
it('builds preferred labels from label linkbase', () => {
const labels = parseLabelLinkbase(SAMPLE_LABEL_LINKBASE);
expect(labels.get('http://fasb.org/us-gaap/2024#Revenues')).toBe('Revenues');
});
it('builds role trees with depth/order/parent metadata', () => {
const rows = parsePresentationLinkbase(SAMPLE_PRESENTATION_LINKBASE);
expect(rows.length).toBe(3);
const root = rows.find((row) => row.qname === 'us-gaap:StatementLineItems');
const revenue = rows.find((row) => row.qname === 'us-gaap:Revenues');
const cogs = rows.find((row) => row.qname === 'us-gaap:CostOfGoodsSold');
expect(root?.depth).toBe(0);
expect(root?.parentConceptKey).toBeNull();
expect(revenue?.depth).toBe(1);
expect(cogs?.depth).toBe(1);
expect(revenue?.parentConceptKey).toBe(root?.conceptKey ?? null);
expect(revenue?.order).toBeLessThan(cogs?.order ?? Number.POSITIVE_INFINITY);
});
it('classifies statement roles into canonical statement kinds', () => {
expect(classifyStatementRole('http://www.xbrl.org/2003/role/StatementOfOperations')).toBe('income');
expect(classifyStatementRole('http://www.xbrl.org/2003/role/StatementOfFinancialPosition')).toBe('balance');
expect(classifyStatementRole('http://www.xbrl.org/2003/role/StatementOfCashFlows')).toBe('cash_flow');
});
});