gloomberb 0.12.0 → 0.13.2
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.
- package/README.md +2 -0
- package/package.json +1 -2
- package/src/api-client/auth.ts +2 -1
- package/src/api-client/index.test.ts +34 -0
- package/src/api-client/index.ts +10 -2
- package/src/api-client/request.ts +4 -1
- package/src/api-client/research-activity.ts +61 -0
- package/src/api-client/types.ts +2 -1
- package/src/cli/commands/plugins.ts +4 -3
- package/src/cli/desktop-pane-shot.ts +79 -10
- package/src/cli/pane-functions/capabilities.ts +76 -23
- package/src/cli/pane-functions/cloud-session.test.ts +54 -0
- package/src/cli/pane-functions/cloud-session.ts +38 -0
- package/src/cli/pane-functions/dom.test.ts +41 -0
- package/src/cli/pane-functions/dom.ts +199 -0
- package/src/cli/pane-functions/index.test.ts +13 -2
- package/src/cli/pane-functions/index.ts +9 -5
- package/src/cli/pane-functions/intraday-shot.test.ts +307 -0
- package/src/cli/pane-functions/intraday-shot.ts +334 -0
- package/src/cli/pane-functions/report.test.ts +29 -1
- package/src/cli/pane-functions/report.ts +68 -7
- package/src/cli/pane-functions/screenshot.ts +195 -42
- package/src/cli/restore-plugins.ts +10 -3
- package/src/components/chart/composite/composite-chart.test.tsx +68 -1
- package/src/components/chart/composite/composite-chart.tsx +87 -21
- package/src/components/chart/composite/rasterizer.ts +33 -0
- package/src/components/chart/composite/renderers.test.ts +51 -4
- package/src/components/chart/composite/scene.test.ts +128 -2
- package/src/components/chart/composite/scene.ts +92 -16
- package/src/components/chart/composite/text-renderer.ts +13 -0
- package/src/components/chart/composite/time-scale.ts +30 -10
- package/src/components/chart/composite/types.ts +26 -0
- package/src/components/command-bar/assist/runtime.ts +38 -30
- package/src/components/command-bar/surface/assist.test.tsx +63 -9
- package/src/components/command-bar/surface/test-harness.tsx +23 -4
- package/src/components/onboarding/account-step/chooser-panel.tsx +2 -2
- package/src/components/onboarding/account-step/qr-panel.tsx +2 -10
- package/src/components/onboarding/onboarding-wizard.test.tsx +38 -12
- package/src/components/onboarding/onboarding-wizard.tsx +49 -14
- package/src/data/config/store/index.test.ts +23 -0
- package/src/data/config/store/node.ts +7 -1
- package/src/data/config/store/normalize.ts +4 -0
- package/src/news/types.ts +1 -0
- package/src/plugins/builtin/cds/client.test.ts +20 -0
- package/src/plugins/builtin/cds/client.ts +13 -3
- package/src/plugins/builtin/cds/headless.test.ts +30 -0
- package/src/plugins/builtin/cds/headless.ts +113 -0
- package/src/plugins/builtin/cds/index.tsx +4 -0
- package/src/plugins/builtin/chart-composer/cli-options.ts +4 -1
- package/src/plugins/builtin/chart-composer/index.tsx +1 -1
- package/src/plugins/builtin/chat/controller/channels.ts +2 -1
- package/src/plugins/builtin/chat/controller/index.ts +13 -5
- package/src/plugins/builtin/chat/controller/realtime.ts +24 -0
- package/src/plugins/builtin/chat/controller/session-runtime.ts +76 -8
- package/src/plugins/builtin/chat/controller/state.ts +1 -0
- package/src/plugins/builtin/chat/controller.test.ts +260 -4
- package/src/plugins/builtin/cloud/askg/executor.test.ts +160 -0
- package/src/plugins/builtin/cloud/askg/executor.ts +429 -0
- package/src/plugins/builtin/cloud/askg/manifest.test.ts +153 -0
- package/src/plugins/builtin/cloud/askg/manifest.ts +314 -0
- package/src/plugins/builtin/cloud/askg/protocol.test.ts +190 -0
- package/src/plugins/builtin/cloud/askg/protocol.ts +286 -0
- package/src/plugins/builtin/cloud/askg/undo.test.ts +44 -0
- package/src/plugins/builtin/cloud/askg/undo.ts +277 -0
- package/src/plugins/builtin/cloud/askg/verify.ts +130 -0
- package/src/plugins/builtin/cloud/auth-dialog.tsx +7 -1
- package/src/plugins/builtin/cloud/device-signin-dialog.tsx +21 -8
- package/src/plugins/builtin/cloud/plugin.tsx +2 -0
- package/src/plugins/builtin/cloud/verification-panel.tsx +56 -0
- package/src/plugins/builtin/congress-trades/client.ts +11 -0
- package/src/plugins/builtin/congress-trades/detail.tsx +3 -3
- package/src/plugins/builtin/congress-trades/headless.test.ts +98 -0
- package/src/plugins/builtin/congress-trades/headless.ts +145 -0
- package/src/plugins/builtin/congress-trades/pane.tsx +3 -3
- package/src/plugins/builtin/credit-conditions/client.ts +1 -1
- package/src/plugins/builtin/credit-conditions/headless.test.ts +31 -0
- package/src/plugins/builtin/credit-conditions/headless.ts +55 -0
- package/src/plugins/builtin/credit-conditions/index.tsx +4 -0
- package/src/plugins/builtin/dividend-yield/headless.test.ts +88 -0
- package/src/plugins/builtin/dividend-yield/headless.ts +135 -0
- package/src/plugins/builtin/dividend-yield/index.tsx +12 -8
- package/src/plugins/builtin/dividend-yield/model.ts +4 -22
- package/src/plugins/builtin/dividend-yield/view.ts +17 -0
- package/src/plugins/builtin/earnings/headless.test.ts +82 -0
- package/src/plugins/builtin/earnings/headless.ts +162 -0
- package/src/plugins/builtin/earnings/index.tsx +2 -0
- package/src/plugins/builtin/earnings-calls/data.ts +29 -8
- package/src/plugins/builtin/earnings-calls/headless.test.ts +192 -0
- package/src/plugins/builtin/earnings-calls/headless.ts +300 -0
- package/src/plugins/builtin/earnings-calls/index.tsx +19 -9
- package/src/plugins/builtin/earnings-calls/model.ts +238 -0
- package/src/plugins/builtin/earnings-calls/pane.tsx +4 -0
- package/src/plugins/builtin/earnings-calls/transcript-view.tsx +13 -11
- package/src/plugins/builtin/fear-greed/headless.test.ts +53 -0
- package/src/plugins/builtin/fear-greed/headless.ts +84 -0
- package/src/plugins/builtin/fear-greed/index.tsx +4 -0
- package/src/plugins/builtin/holders/client.ts +29 -0
- package/src/plugins/builtin/holders/headless.test.ts +58 -0
- package/src/plugins/builtin/holders/headless.ts +162 -0
- package/src/plugins/builtin/holders/index.tsx +14 -8
- package/src/plugins/builtin/holders/pane.tsx +7 -1
- package/src/plugins/builtin/insider/client.ts +50 -0
- package/src/plugins/builtin/insider/headless.test.ts +122 -0
- package/src/plugins/builtin/insider/headless.ts +117 -0
- package/src/plugins/builtin/insider/index.tsx +21 -50
- package/src/plugins/builtin/insider/insider-data.ts +1 -1
- package/src/plugins/builtin/insider/model.ts +72 -0
- package/src/plugins/builtin/ipo-calendar/client.ts +15 -0
- package/src/plugins/builtin/ipo-calendar/headless.test.ts +89 -0
- package/src/plugins/builtin/ipo-calendar/headless.ts +127 -0
- package/src/plugins/builtin/ipo-calendar/index.tsx +2 -0
- package/src/plugins/builtin/ipo-calendar/model.ts +3 -15
- package/src/plugins/builtin/market-movers/client.ts +76 -0
- package/src/plugins/builtin/market-movers/headless.test.ts +34 -0
- package/src/plugins/builtin/market-movers/headless.ts +73 -0
- package/src/plugins/builtin/market-movers/index.tsx +11 -51
- package/src/plugins/builtin/news/headless.test.ts +138 -0
- package/src/plugins/builtin/news/headless.ts +190 -0
- package/src/plugins/builtin/news/index.tsx +13 -9
- package/src/plugins/builtin/news/wire/index.tsx +2 -1
- package/src/plugins/builtin/research/analyst-headless.test.ts +60 -0
- package/src/plugins/builtin/research/analyst-headless.ts +172 -0
- package/src/plugins/builtin/research/analyst-model.ts +219 -0
- package/src/plugins/builtin/research/analyst-pane.tsx +31 -221
- package/src/plugins/builtin/research/client.ts +12 -0
- package/src/plugins/builtin/research/corporate-actions-pane.tsx +3 -237
- package/src/plugins/builtin/research/event-model.ts +245 -0
- package/src/plugins/builtin/research/events-headless.test.ts +32 -0
- package/src/plugins/builtin/research/events-headless.ts +87 -0
- package/src/plugins/builtin/research/headless.test.ts +93 -0
- package/src/plugins/builtin/research/headless.ts +161 -0
- package/src/plugins/builtin/research/index.tsx +32 -18
- package/src/plugins/builtin/research-search/pane.tsx +4 -0
- package/src/plugins/builtin/scanner/headless.test.ts +59 -0
- package/src/plugins/builtin/scanner/headless.ts +125 -0
- package/src/plugins/builtin/scanner/index.ts +4 -0
- package/src/plugins/builtin/sec/client.ts +16 -0
- package/src/plugins/builtin/sec/headless.test.ts +72 -0
- package/src/plugins/builtin/sec/headless.ts +69 -0
- package/src/plugins/builtin/sec/index.tsx +20 -75
- package/src/plugins/builtin/sec/model.ts +89 -0
- package/src/plugins/builtin/sectors/client.ts +54 -0
- package/src/plugins/builtin/sectors/headless.test.ts +28 -0
- package/src/plugins/builtin/sectors/headless.ts +80 -0
- package/src/plugins/builtin/sectors/index.tsx +6 -51
- package/src/plugins/builtin/shared/cloud-upgrade.ts +8 -4
- package/src/plugins/builtin/short-interest/client.ts +5 -2
- package/src/plugins/builtin/short-interest/headless.test.ts +96 -0
- package/src/plugins/builtin/short-interest/headless.ts +111 -0
- package/src/plugins/builtin/short-interest/index.tsx +15 -9
- package/src/plugins/builtin/thirteenf/data.ts +13 -11
- package/src/plugins/builtin/thirteenf/headless.test.ts +112 -0
- package/src/plugins/builtin/thirteenf/headless.ts +286 -0
- package/src/plugins/builtin/thirteenf/index.tsx +4 -0
- package/src/plugins/builtin/thirteenf/model.ts +13 -0
- package/src/plugins/builtin/thirteenf/table.ts +3 -15
- package/src/plugins/builtin/ticker-detail/pane.tsx +14 -0
- package/src/plugins/builtin/treasury-auctions/headless.test.ts +34 -0
- package/src/plugins/builtin/treasury-auctions/headless.ts +95 -0
- package/src/plugins/builtin/treasury-auctions/index.tsx +4 -0
- package/src/plugins/builtin/volatility/client.ts +16 -3
- package/src/plugins/builtin/volatility/headless.test.ts +35 -0
- package/src/plugins/builtin/volatility/headless.ts +75 -0
- package/src/plugins/builtin/volatility/index.tsx +4 -0
- package/src/plugins/builtin/world-indices/client.ts +43 -0
- package/src/plugins/builtin/world-indices/headless.test.ts +32 -0
- package/src/plugins/builtin/world-indices/headless.ts +109 -0
- package/src/plugins/builtin/world-indices/index.tsx +4 -0
- package/src/plugins/builtin/yield-curve/headless.test.ts +24 -0
- package/src/plugins/builtin/yield-curve/headless.ts +67 -0
- package/src/plugins/builtin/yield-curve/index.tsx +6 -8
- package/src/plugins/builtin/yield-curve/treasury-data.ts +16 -6
- package/src/plugins/bundle.test.ts +58 -1
- package/src/plugins/bundle.ts +4 -2
- package/src/plugins/catalog-backend.test.ts +26 -0
- package/src/plugins/catalog-backend.ts +9 -2
- package/src/plugins/catalog-browser.test.ts +0 -13
- package/src/plugins/catalog-browser.ts +4 -0
- package/src/plugins/catalog-manifest.test.ts +15 -0
- package/src/plugins/catalog-manifest.ts +12 -5
- package/src/plugins/catalog-ui.ts +0 -6
- package/src/plugins/host-contract.ts +9 -0
- package/src/plugins/host-modules.ts +12 -0
- package/src/plugins/loader.ts +63 -15
- package/src/plugins/prediction-markets/headless.test.ts +119 -0
- package/src/plugins/prediction-markets/headless.ts +255 -0
- package/src/plugins/prediction-markets/index.tsx +2 -0
- package/src/plugins/seed.ts +3 -0
- package/src/public/broker.ts +6 -0
- package/src/remote/schema.test.ts +65 -0
- package/src/remote/schema.ts +322 -39
- package/src/remote/types.ts +49 -2
- package/src/renderers/browser/app-services.ts +3 -0
- package/src/renderers/browser/config-host.ts +24 -8
- package/src/renderers/browser/deeplink-bridge.test.ts +11 -0
- package/src/renderers/browser/deeplink-bridge.ts +15 -3
- package/src/renderers/browser/main.tsx +3 -1
- package/src/renderers/browser/research-entry.test.ts +8 -0
- package/src/renderers/browser/research-entry.ts +7 -0
- package/src/renderers/electrobun/bun/desktop/backend-requests.ts +2 -2
- package/src/renderers/electrobun/bun/desktop/initialization.ts +10 -3
- package/src/renderers/electrobun/bun/external-plugins.ts +20 -4
- package/src/renderers/electrobun/view/build-assets.ts +0 -2
- package/src/renderers/electrobun/view/cli-pane-shot-entry.tsx +63 -10
- package/src/sources/gloomberb-cloud/news.ts +1 -0
- package/src/sync/core-contributors.test.ts +111 -0
- package/src/sync/core-contributors.ts +155 -1
- package/src/time-series/resolve.test.ts +118 -1
- package/src/time-series/resolve.ts +14 -16
- package/src/types/config.ts +2 -0
- package/src/plugins/broker-sync/broker-sync.test.ts +0 -188
- package/src/plugins/broker-sync/native-loader.ts +0 -10
- package/src/plugins/broker-sync/normalize.ts +0 -308
- package/src/plugins/broker-sync/public.ts +0 -122
- package/src/plugins/broker-sync/robinhood-native.ts +0 -367
- package/src/plugins/broker-sync/robinhood.ts +0 -114
- package/src/plugins/broker-sync/simplefin-native.ts +0 -201
- package/src/plugins/broker-sync/simplefin.ts +0 -70
- package/src/renderers/electrobun/view/native-stubs/broker-sync-native-loader.ts +0 -7
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
3
|
+
import {
|
|
4
|
+
createEarningsEstimatesHeadless,
|
|
5
|
+
type EarningsEstimateSources,
|
|
6
|
+
} from "./headless";
|
|
7
|
+
|
|
8
|
+
const sources: EarningsEstimateSources = {
|
|
9
|
+
currency: "USD",
|
|
10
|
+
actions: {
|
|
11
|
+
symbol: "AMD",
|
|
12
|
+
currency: "USD",
|
|
13
|
+
dividends: [],
|
|
14
|
+
splits: [],
|
|
15
|
+
earnings: [{
|
|
16
|
+
date: "2026-07-28",
|
|
17
|
+
epsEstimate: 0.45,
|
|
18
|
+
epsActual: 0.48,
|
|
19
|
+
difference: 0.03,
|
|
20
|
+
surprisePercent: 6.67,
|
|
21
|
+
}],
|
|
22
|
+
},
|
|
23
|
+
estimates: {
|
|
24
|
+
symbol: "AMD",
|
|
25
|
+
currency: "USD",
|
|
26
|
+
recommendations: [],
|
|
27
|
+
ratings: [],
|
|
28
|
+
earningsEstimates: [
|
|
29
|
+
{ date: "2026-09-30", period: "next_quarter", average: 1.2, analysts: 30 },
|
|
30
|
+
{ date: "2026-12-31", period: "current_year", average: 4.8, analysts: 32 },
|
|
31
|
+
],
|
|
32
|
+
revenueEstimates: [
|
|
33
|
+
{ date: "2026-09-30", period: "next_quarter", average: 9_000_000_000, analysts: 28 },
|
|
34
|
+
{ date: "2026-12-31", period: "current_year", average: 35_000_000_000, analysts: 29 },
|
|
35
|
+
],
|
|
36
|
+
},
|
|
37
|
+
financials: {
|
|
38
|
+
annualStatements: [],
|
|
39
|
+
quarterlyStatements: [],
|
|
40
|
+
priceHistory: [],
|
|
41
|
+
},
|
|
42
|
+
};
|
|
43
|
+
|
|
44
|
+
function args(options: Record<string, string | number | boolean>): HeadlessPaneLoadArgs {
|
|
45
|
+
return {
|
|
46
|
+
rawArgument: "AMD",
|
|
47
|
+
argument: "AMD",
|
|
48
|
+
symbols: ["AMD"],
|
|
49
|
+
options,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
const context = {} as HeadlessPaneContext;
|
|
54
|
+
|
|
55
|
+
describe("earnings estimates headless", () => {
|
|
56
|
+
test("maps the shared event model into estimate rows", async () => {
|
|
57
|
+
const definition = createEarningsEstimatesHeadless({
|
|
58
|
+
loadSources: async () => sources,
|
|
59
|
+
});
|
|
60
|
+
|
|
61
|
+
const result = await definition.load(args({ kind: "all", limit: 50 }), context);
|
|
62
|
+
|
|
63
|
+
expect(result.rows).toEqual(expect.arrayContaining([
|
|
64
|
+
expect.objectContaining({
|
|
65
|
+
status: "Q Est",
|
|
66
|
+
period: "next qtr",
|
|
67
|
+
qEps: 1.2,
|
|
68
|
+
qRevenue: 9_000_000_000,
|
|
69
|
+
detail: "30E/28R",
|
|
70
|
+
}),
|
|
71
|
+
expect.objectContaining({
|
|
72
|
+
status: "FY Est",
|
|
73
|
+
annualEps: 4.8,
|
|
74
|
+
annualRevenue: 35_000_000_000,
|
|
75
|
+
}),
|
|
76
|
+
expect.objectContaining({
|
|
77
|
+
status: "Earnings",
|
|
78
|
+
qEps: 0.48,
|
|
79
|
+
value: "+6.67%",
|
|
80
|
+
}),
|
|
81
|
+
]));
|
|
82
|
+
});
|
|
83
|
+
|
|
84
|
+
test("applies kind and limit options", async () => {
|
|
85
|
+
const definition = createEarningsEstimatesHeadless({ loadSources: async () => sources });
|
|
86
|
+
|
|
87
|
+
const result = await definition.load(args({ kind: "estimates", limit: 1 }), context);
|
|
88
|
+
|
|
89
|
+
expect(result.rows).toHaveLength(1);
|
|
90
|
+
expect(["Q Est", "FY Est"]).toContain(result.rows[0]?.status);
|
|
91
|
+
expect(result.metadata).toMatchObject({ kind: "estimates", total: 2, truncated: true });
|
|
92
|
+
});
|
|
93
|
+
});
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
AnalystResearchData,
|
|
3
|
+
CorporateActionsData,
|
|
4
|
+
TickerFinancials,
|
|
5
|
+
} from "../../../types/financials";
|
|
6
|
+
import type {
|
|
7
|
+
HeadlessPaneContext,
|
|
8
|
+
HeadlessPaneDefinition,
|
|
9
|
+
HeadlessPaneLoadArgs,
|
|
10
|
+
HeadlessRowsResult,
|
|
11
|
+
} from "../../../types/plugin";
|
|
12
|
+
import { buildEventRows, type EventRow } from "./event-model";
|
|
13
|
+
|
|
14
|
+
const ESTIMATE_COLUMNS = [
|
|
15
|
+
{ key: "date", header: "Date" },
|
|
16
|
+
{ key: "status", header: "Event" },
|
|
17
|
+
{ key: "period", header: "Period" },
|
|
18
|
+
{ key: "qEps", header: "Q EPS", align: "right" as const },
|
|
19
|
+
{ key: "qRevenue", header: "Q revenue", align: "right" as const },
|
|
20
|
+
{ key: "annualEps", header: "Annual EPS", align: "right" as const },
|
|
21
|
+
{ key: "annualRevenue", header: "Annual revenue", align: "right" as const },
|
|
22
|
+
{ key: "value", header: "Value", align: "right" as const },
|
|
23
|
+
{ key: "detail", header: "Detail" },
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
export interface EarningsEstimateSources {
|
|
27
|
+
actions: CorporateActionsData | null;
|
|
28
|
+
estimates: AnalystResearchData | null;
|
|
29
|
+
financials: TickerFinancials | null;
|
|
30
|
+
currency: string;
|
|
31
|
+
errors?: string[];
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
export interface EarningsEstimatesHeadlessDependencies {
|
|
35
|
+
loadSources(
|
|
36
|
+
symbol: string,
|
|
37
|
+
context: HeadlessPaneContext,
|
|
38
|
+
): Promise<EarningsEstimateSources>;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
async function settledValue<T>(
|
|
42
|
+
promise: Promise<T> | null,
|
|
43
|
+
errors: string[],
|
|
44
|
+
label: string,
|
|
45
|
+
): Promise<T | null> {
|
|
46
|
+
if (!promise) return null;
|
|
47
|
+
try {
|
|
48
|
+
return await promise;
|
|
49
|
+
} catch (error) {
|
|
50
|
+
errors.push(`${label}: ${error instanceof Error ? error.message : String(error)}`);
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
const defaultDependencies: EarningsEstimatesHeadlessDependencies = {
|
|
56
|
+
async loadSources(symbol, context) {
|
|
57
|
+
const errors: string[] = [];
|
|
58
|
+
const provider = context.marketData;
|
|
59
|
+
const [actions, estimates, financials] = await Promise.all([
|
|
60
|
+
settledValue(
|
|
61
|
+
provider.getCorporateActions?.(symbol, "") ?? null,
|
|
62
|
+
errors,
|
|
63
|
+
"corporate actions",
|
|
64
|
+
),
|
|
65
|
+
settledValue(
|
|
66
|
+
provider.getAnalystResearch?.(symbol, "") ?? null,
|
|
67
|
+
errors,
|
|
68
|
+
"analyst estimates",
|
|
69
|
+
),
|
|
70
|
+
settledValue(provider.getTickerFinancials(symbol, ""), errors, "financials"),
|
|
71
|
+
]);
|
|
72
|
+
if (!actions && !estimates && !financials && errors.length > 0) {
|
|
73
|
+
throw new Error(errors.join("; "));
|
|
74
|
+
}
|
|
75
|
+
return {
|
|
76
|
+
actions,
|
|
77
|
+
estimates,
|
|
78
|
+
financials,
|
|
79
|
+
currency: actions?.currency ?? estimates?.currency ?? financials?.quote?.currency ?? "USD",
|
|
80
|
+
errors,
|
|
81
|
+
};
|
|
82
|
+
},
|
|
83
|
+
};
|
|
84
|
+
|
|
85
|
+
function matchesKind(row: EventRow, kind: string): boolean {
|
|
86
|
+
if (kind === "all") return true;
|
|
87
|
+
if (kind === "estimates") return row.status === "Q Est" || row.status === "FY Est";
|
|
88
|
+
return row.status === "Earnings" || row.status === "TTM";
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
export function projectEarningsEstimatesHeadless(
|
|
92
|
+
sources: EarningsEstimateSources,
|
|
93
|
+
args: HeadlessPaneLoadArgs,
|
|
94
|
+
): HeadlessRowsResult {
|
|
95
|
+
const kind = String(args.options.kind ?? "all");
|
|
96
|
+
const limit = Number(args.options.limit ?? 50);
|
|
97
|
+
const matching = buildEventRows(
|
|
98
|
+
sources.actions,
|
|
99
|
+
sources.estimates,
|
|
100
|
+
sources.financials,
|
|
101
|
+
sources.currency,
|
|
102
|
+
).filter((row) => matchesKind(row, kind));
|
|
103
|
+
const rows = matching.slice(0, limit).map((row) => ({ ...row }));
|
|
104
|
+
|
|
105
|
+
return {
|
|
106
|
+
columns: ESTIMATE_COLUMNS,
|
|
107
|
+
rows,
|
|
108
|
+
errors: sources.errors?.length ? sources.errors : undefined,
|
|
109
|
+
metadata: {
|
|
110
|
+
currency: sources.currency,
|
|
111
|
+
kind,
|
|
112
|
+
total: matching.length,
|
|
113
|
+
returned: rows.length,
|
|
114
|
+
truncated: rows.length < matching.length,
|
|
115
|
+
},
|
|
116
|
+
};
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
export function createEarningsEstimatesHeadless(
|
|
120
|
+
dependencies: EarningsEstimatesHeadlessDependencies = defaultDependencies,
|
|
121
|
+
): HeadlessPaneDefinition<"rows"> {
|
|
122
|
+
return {
|
|
123
|
+
shape: "rows",
|
|
124
|
+
argument: {
|
|
125
|
+
kind: "ticker",
|
|
126
|
+
placeholder: "ticker",
|
|
127
|
+
description: "Company symbol.",
|
|
128
|
+
},
|
|
129
|
+
options: [
|
|
130
|
+
{
|
|
131
|
+
key: "kind",
|
|
132
|
+
description: "Estimate or reported rows to include.",
|
|
133
|
+
type: "enum",
|
|
134
|
+
values: [
|
|
135
|
+
{ value: "all" },
|
|
136
|
+
{ value: "estimates" },
|
|
137
|
+
{ value: "reported" },
|
|
138
|
+
],
|
|
139
|
+
defaultValue: "all",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
key: "limit",
|
|
143
|
+
description: "Maximum rows to return.",
|
|
144
|
+
type: "integer",
|
|
145
|
+
defaultValue: 50,
|
|
146
|
+
minimum: 1,
|
|
147
|
+
maximum: 200,
|
|
148
|
+
},
|
|
149
|
+
],
|
|
150
|
+
columns: ESTIMATE_COLUMNS,
|
|
151
|
+
describe: (args) => `Earnings Estimates | ${String(args.argument)}`,
|
|
152
|
+
async load(args, context) {
|
|
153
|
+
return projectEarningsEstimatesHeadless(
|
|
154
|
+
await dependencies.loadSources(String(args.argument), context),
|
|
155
|
+
args,
|
|
156
|
+
);
|
|
157
|
+
},
|
|
158
|
+
};
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export const earningsEstimatesHeadless = createEarningsEstimatesHeadless();
|
|
@@ -5,6 +5,13 @@ import { AnalystResearchView } from "./analyst-pane";
|
|
|
5
5
|
import { CorporateActionsView } from "./corporate-actions-pane";
|
|
6
6
|
import { EquityDiagnosticView } from "./equity-diagnostic-pane";
|
|
7
7
|
import { RelativeValuationPane } from "./relative-valuation-pane";
|
|
8
|
+
import { analystResearchHeadless } from "./analyst-headless";
|
|
9
|
+
import { eventsHeadless } from "./events-headless";
|
|
10
|
+
import { earningsEstimatesHeadless } from "./headless";
|
|
11
|
+
|
|
12
|
+
export { analystResearchHeadless } from "./analyst-headless";
|
|
13
|
+
export { eventsHeadless } from "./events-headless";
|
|
14
|
+
export { earningsEstimatesHeadless } from "./headless";
|
|
8
15
|
|
|
9
16
|
function EarningsEstimatesPane(props: { focused: boolean; width: number; height: number }) {
|
|
10
17
|
return (
|
|
@@ -70,6 +77,7 @@ export const researchModule: PluginModule = {
|
|
|
70
77
|
defaultMode: "floating",
|
|
71
78
|
defaultFloatingSize: { width: 104, height: 24 },
|
|
72
79
|
tableExport: true,
|
|
80
|
+
headless: eventsHeadless,
|
|
73
81
|
},
|
|
74
82
|
{
|
|
75
83
|
id: "relative-valuation",
|
|
@@ -94,15 +102,18 @@ export const researchModule: PluginModule = {
|
|
|
94
102
|
],
|
|
95
103
|
|
|
96
104
|
paneTemplates: [
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
105
|
+
{
|
|
106
|
+
...createTickerSurfacePaneTemplate({
|
|
107
|
+
id: "analyst-research-pane",
|
|
108
|
+
paneId: "analyst-research",
|
|
109
|
+
label: "Analyst Research",
|
|
110
|
+
description: "Price targets, recommendations, and recent analyst actions.",
|
|
111
|
+
keywords: ["analyst", "research", "ratings", "target", "anr"],
|
|
112
|
+
shortcut: "ANR",
|
|
113
|
+
publicShare: true,
|
|
114
|
+
}),
|
|
115
|
+
headless: analystResearchHeadless,
|
|
116
|
+
},
|
|
106
117
|
createTickerSurfacePaneTemplate({
|
|
107
118
|
id: "equity-diagnostic-pane",
|
|
108
119
|
paneId: "equity-diagnostic",
|
|
@@ -121,15 +132,18 @@ export const researchModule: PluginModule = {
|
|
|
121
132
|
shortcut: "EVT",
|
|
122
133
|
publicShare: true,
|
|
123
134
|
}),
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
135
|
+
{
|
|
136
|
+
...createTickerSurfacePaneTemplate({
|
|
137
|
+
id: "earnings-estimates-pane",
|
|
138
|
+
paneId: "earnings-estimates",
|
|
139
|
+
label: "Earnings Estimates",
|
|
140
|
+
description: "EPS and revenue estimates with reported earnings.",
|
|
141
|
+
keywords: ["earnings", "estimates", "ee", "analyst", "eps", "revenue", "events"],
|
|
142
|
+
shortcut: "EE",
|
|
143
|
+
publicShare: true,
|
|
144
|
+
}),
|
|
145
|
+
headless: earningsEstimatesHeadless,
|
|
146
|
+
},
|
|
133
147
|
{
|
|
134
148
|
id: "relative-valuation-pane",
|
|
135
149
|
paneId: "relative-valuation",
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import { recordResearchActivity } from "../../../api-client/research-activity";
|
|
1
2
|
import { useCallback, useEffect, useMemo, useRef, useState } from "react";
|
|
2
3
|
import {
|
|
3
4
|
Button,
|
|
@@ -416,6 +417,9 @@ export function ResearchSearchPane({ focused, paneId, width, height }: PaneProps
|
|
|
416
417
|
// Search is free and uncapped, so nothing is gated up front: the upsell only
|
|
417
418
|
// appears if the server itself refuses the query.
|
|
418
419
|
const proRequired = failure?.status === 402;
|
|
420
|
+
useEffect(() => {
|
|
421
|
+
if (focused && status === "loaded") recordResearchActivity("research_viewed", "search");
|
|
422
|
+
}, [focused, status]);
|
|
419
423
|
const signInRequired = !access.signedIn || failure?.status === 401 || savedFailure?.status === 401;
|
|
420
424
|
const verificationRequired = !signInRequired
|
|
421
425
|
&& (!access.emailVerified || failure?.status === 403);
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import type { ScannerHiloPayload } from "../../../api-client";
|
|
3
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
4
|
+
import { createHiloHeadless, loadHiloSnapshot } from "./headless";
|
|
5
|
+
|
|
6
|
+
const payload: ScannerHiloPayload = {
|
|
7
|
+
status: "live",
|
|
8
|
+
asOf: 123,
|
|
9
|
+
access: "realtime",
|
|
10
|
+
delayMinutes: 0,
|
|
11
|
+
windows: { s30: { highs: 1, lows: 1 }, m1: { highs: 2, lows: 2 }, m5: { highs: 3, lows: 3 } },
|
|
12
|
+
lows: [
|
|
13
|
+
{ symbol: "PENNY", price: 0.5, count: 10, at: 10 },
|
|
14
|
+
{ symbol: "LOW", price: 10, count: 2, at: 20 },
|
|
15
|
+
],
|
|
16
|
+
highs: [{ symbol: "HIGH", price: 20, count: 5, at: 30 }],
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
function args(minPrice: string, sort: string): HeadlessPaneLoadArgs {
|
|
20
|
+
return { rawArgument: "", argument: null, symbols: [], options: { minPrice, sort } };
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
describe("highs and lows headless model", () => {
|
|
24
|
+
test("waits for scanner data beyond the connecting snapshot", async () => {
|
|
25
|
+
let unsubscribed = 0;
|
|
26
|
+
const result = await loadHiloSnapshot({
|
|
27
|
+
subscribeScanner(_scanner, listener) {
|
|
28
|
+
listener({ type: "data", payload: { ...payload, status: "starting" } });
|
|
29
|
+
listener({ type: "data", payload });
|
|
30
|
+
return () => { unsubscribed += 1; };
|
|
31
|
+
},
|
|
32
|
+
}, new AbortController().signal);
|
|
33
|
+
|
|
34
|
+
expect(result.status).toBe("live");
|
|
35
|
+
expect(unsubscribed).toBe(1);
|
|
36
|
+
});
|
|
37
|
+
|
|
38
|
+
test("reports a degraded stream as partial data", async () => {
|
|
39
|
+
const headless = createHiloHeadless({
|
|
40
|
+
load: async () => ({ ...payload, status: "degraded" }),
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
const result = await headless.load(args("1", "recent"), {} as HeadlessPaneContext);
|
|
44
|
+
|
|
45
|
+
expect(result.errors).toEqual(["Scanner feed degraded"]);
|
|
46
|
+
});
|
|
47
|
+
|
|
48
|
+
test("applies the pane price and sort options to a stream snapshot", async () => {
|
|
49
|
+
const headless = createHiloHeadless({ load: async () => payload });
|
|
50
|
+
const filtered = await headless.load(args("1", "recent"), {} as HeadlessPaneContext);
|
|
51
|
+
const unfiltered = await headless.load(args("off", "count"), {} as HeadlessPaneContext);
|
|
52
|
+
|
|
53
|
+
expect(filtered.items.map((row) => row.symbol)).toEqual(["LOW", "HIGH"]);
|
|
54
|
+
expect(unfiltered.items.map((row) => row.symbol)).toEqual(["PENNY", "LOW", "HIGH"]);
|
|
55
|
+
expect((filtered.metadata?.windows as Array<Record<string, unknown>>)[0]).toMatchObject({
|
|
56
|
+
key: "m5", highs: 3, lows: 3,
|
|
57
|
+
});
|
|
58
|
+
});
|
|
59
|
+
});
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
import type { ScannerFeedEvent, ScannerHiloPayload } from "../../../api-client";
|
|
2
|
+
import type {
|
|
3
|
+
HeadlessPaneApiClient,
|
|
4
|
+
HeadlessPaneDefinition,
|
|
5
|
+
HeadlessPaneLoadArgs,
|
|
6
|
+
HeadlessSnapshotResult,
|
|
7
|
+
} from "../../../types/plugin";
|
|
8
|
+
import { buildHiloBarRows, filterHiloRows, type HiloMinPrice, type HiloSort } from "./hilo-model";
|
|
9
|
+
|
|
10
|
+
export function loadHiloSnapshot(
|
|
11
|
+
client: Pick<HeadlessPaneApiClient, "subscribeScanner">,
|
|
12
|
+
signal: AbortSignal,
|
|
13
|
+
timeoutMs = 10_000,
|
|
14
|
+
): Promise<ScannerHiloPayload> {
|
|
15
|
+
return new Promise((resolve, reject) => {
|
|
16
|
+
let settled = false;
|
|
17
|
+
let unsubscribe: (() => void) | null = null;
|
|
18
|
+
const finish = (callback: () => void) => {
|
|
19
|
+
if (settled) return;
|
|
20
|
+
settled = true;
|
|
21
|
+
clearTimeout(timer);
|
|
22
|
+
signal.removeEventListener("abort", onAbort);
|
|
23
|
+
unsubscribe?.();
|
|
24
|
+
callback();
|
|
25
|
+
};
|
|
26
|
+
const onAbort = () => finish(() => reject(signal.reason instanceof Error
|
|
27
|
+
? signal.reason
|
|
28
|
+
: new Error("Highs and lows snapshot aborted")));
|
|
29
|
+
const timer = setTimeout(() => finish(() => reject(new Error("Highs and lows snapshot timed out"))), timeoutMs);
|
|
30
|
+
signal.addEventListener("abort", onAbort, { once: true });
|
|
31
|
+
unsubscribe = client.subscribeScanner("hilo", (event: ScannerFeedEvent) => {
|
|
32
|
+
if (event.type === "denied") {
|
|
33
|
+
finish(() => reject(new Error(event.reason === "pro_required" ? "Pro access required" : "Sign in required")));
|
|
34
|
+
return;
|
|
35
|
+
}
|
|
36
|
+
if (event.payload.status === "starting") return;
|
|
37
|
+
finish(() => resolve(event.payload as ScannerHiloPayload));
|
|
38
|
+
});
|
|
39
|
+
if (settled) unsubscribe();
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
export function projectHiloSnapshot(
|
|
44
|
+
payload: ScannerHiloPayload,
|
|
45
|
+
minPrice: HiloMinPrice,
|
|
46
|
+
sort: HiloSort,
|
|
47
|
+
): HeadlessSnapshotResult {
|
|
48
|
+
const lows = filterHiloRows(payload.lows, minPrice, sort).map((row) => ({ side: "low", ...row }));
|
|
49
|
+
const highs = filterHiloRows(payload.highs, minPrice, sort).map((row) => ({ side: "high", ...row }));
|
|
50
|
+
return {
|
|
51
|
+
asOf: new Date(payload.asOf).toISOString(),
|
|
52
|
+
items: [...lows, ...highs],
|
|
53
|
+
...(payload.status === "degraded" ? { errors: ["Scanner feed degraded"] } : {}),
|
|
54
|
+
metadata: {
|
|
55
|
+
status: payload.status,
|
|
56
|
+
access: payload.access,
|
|
57
|
+
delayMinutes: payload.delayMinutes,
|
|
58
|
+
windows: buildHiloBarRows(payload.windows),
|
|
59
|
+
minPrice,
|
|
60
|
+
sort,
|
|
61
|
+
},
|
|
62
|
+
};
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
export interface HiloHeadlessDependencies {
|
|
66
|
+
load(
|
|
67
|
+
args: HeadlessPaneLoadArgs,
|
|
68
|
+
client: Pick<HeadlessPaneApiClient, "subscribeScanner">,
|
|
69
|
+
signal: AbortSignal,
|
|
70
|
+
): Promise<ScannerHiloPayload>;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
const defaultDependencies: HiloHeadlessDependencies = {
|
|
74
|
+
load: (_args, client, signal) => loadHiloSnapshot(client, signal),
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
export function createHiloHeadless(
|
|
78
|
+
dependencies: HiloHeadlessDependencies = defaultDependencies,
|
|
79
|
+
): HeadlessPaneDefinition<"snapshot"> {
|
|
80
|
+
return {
|
|
81
|
+
shape: "snapshot",
|
|
82
|
+
argument: { kind: "none" },
|
|
83
|
+
options: [
|
|
84
|
+
{
|
|
85
|
+
key: "minPrice",
|
|
86
|
+
aliases: ["min-price"],
|
|
87
|
+
settingKey: "minPrice",
|
|
88
|
+
description: "Minimum stock price.",
|
|
89
|
+
type: "enum",
|
|
90
|
+
values: [{ value: "off" }, { value: "1" }, { value: "5" }],
|
|
91
|
+
defaultValue: "1",
|
|
92
|
+
},
|
|
93
|
+
{
|
|
94
|
+
key: "sort",
|
|
95
|
+
settingKey: "sort",
|
|
96
|
+
description: "Extreme ordering.",
|
|
97
|
+
type: "enum",
|
|
98
|
+
values: [{ value: "recent" }, { value: "count" }],
|
|
99
|
+
defaultValue: "recent",
|
|
100
|
+
},
|
|
101
|
+
],
|
|
102
|
+
columns: [
|
|
103
|
+
{ key: "side", header: "Side" },
|
|
104
|
+
{ key: "symbol", header: "Symbol" },
|
|
105
|
+
{ key: "price", header: "Price", align: "right" },
|
|
106
|
+
{ key: "count", header: "Count", align: "right" },
|
|
107
|
+
{
|
|
108
|
+
key: "at",
|
|
109
|
+
header: "Observed at",
|
|
110
|
+
format: (value: unknown) => new Date(Number(value)).toISOString(),
|
|
111
|
+
},
|
|
112
|
+
],
|
|
113
|
+
describe: "New Highs / Lows",
|
|
114
|
+
async load(args, ctx) {
|
|
115
|
+
const payload = await dependencies.load(args, ctx.apiClient, ctx.signal);
|
|
116
|
+
return projectHiloSnapshot(
|
|
117
|
+
payload,
|
|
118
|
+
args.options.minPrice as HiloMinPrice,
|
|
119
|
+
args.options.sort as HiloSort,
|
|
120
|
+
);
|
|
121
|
+
},
|
|
122
|
+
};
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
export const hiloHeadless = createHiloHeadless();
|
|
@@ -3,6 +3,9 @@ import type { PluginModule } from "../plugin-module";
|
|
|
3
3
|
import { DEFAULT_FLOW_FILTERS, FLOW_FILTER_OPTIONS } from "./flow-model";
|
|
4
4
|
import FlowPane from "./flow-pane";
|
|
5
5
|
import HiloPane from "./hilo-pane";
|
|
6
|
+
import { hiloHeadless } from "./headless";
|
|
7
|
+
|
|
8
|
+
export { hiloHeadless } from "./headless";
|
|
6
9
|
|
|
7
10
|
export const HILO_PANE_ID = "scanner-hilo";
|
|
8
11
|
export const FLOW_PANE_ID = "scanner-flow";
|
|
@@ -96,6 +99,7 @@ export const scannerModule: PluginModule = {
|
|
|
96
99
|
description: "Session new-high and new-low momentum with 30s/1m/5m counts.",
|
|
97
100
|
keywords: ["hilo", "highs", "lows", "new", "momentum", "breakout", "scanner"],
|
|
98
101
|
shortcut: { prefix: "HILO" },
|
|
102
|
+
headless: hiloHeadless,
|
|
99
103
|
createInstance: () => ({ settings: { minPrice: "1", sort: "recent" } }),
|
|
100
104
|
},
|
|
101
105
|
{
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type {
|
|
2
|
+
DataProvider,
|
|
3
|
+
MarketDataRequestContext,
|
|
4
|
+
SecFilingItem,
|
|
5
|
+
} from "../../../types/data-provider";
|
|
6
|
+
|
|
7
|
+
export async function loadSecFilings(
|
|
8
|
+
provider: DataProvider,
|
|
9
|
+
symbol: string,
|
|
10
|
+
count: number,
|
|
11
|
+
exchange = "",
|
|
12
|
+
context?: MarketDataRequestContext,
|
|
13
|
+
): Promise<SecFilingItem[]> {
|
|
14
|
+
if (!provider.getSecFilings) throw new Error("SEC filing data unavailable");
|
|
15
|
+
return provider.getSecFilings(symbol, count, exchange, context);
|
|
16
|
+
}
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { createTestDataProvider } from "../../../test-support/data-provider";
|
|
3
|
+
import { createDefaultConfig } from "../../../types/config";
|
|
4
|
+
import type { SecFilingItem } from "../../../types/data-provider";
|
|
5
|
+
import type { HeadlessPaneContext, HeadlessPaneLoadArgs } from "../../../types/plugin";
|
|
6
|
+
import { createSecHeadless } from "./headless";
|
|
7
|
+
|
|
8
|
+
const filings: SecFilingItem[] = [
|
|
9
|
+
{
|
|
10
|
+
accessionNumber: "0000320193-26-000100",
|
|
11
|
+
form: "8-K",
|
|
12
|
+
filingDate: new Date("2026-08-25T00:00:00.000Z"),
|
|
13
|
+
primaryDocDescription: "8-K Results of Operations",
|
|
14
|
+
items: "2.02,9.01",
|
|
15
|
+
cik: "0000320193",
|
|
16
|
+
filingUrl: "https://www.sec.gov/filing/one",
|
|
17
|
+
},
|
|
18
|
+
{
|
|
19
|
+
accessionNumber: "0000320193-26-000099",
|
|
20
|
+
form: "10-Q",
|
|
21
|
+
filingDate: new Date("2026-08-20T00:00:00.000Z"),
|
|
22
|
+
cik: "0000320193",
|
|
23
|
+
filingUrl: "https://www.sec.gov/filing/two",
|
|
24
|
+
},
|
|
25
|
+
];
|
|
26
|
+
|
|
27
|
+
function context(): HeadlessPaneContext {
|
|
28
|
+
return {
|
|
29
|
+
marketData: createTestDataProvider(),
|
|
30
|
+
apiClient: {} as HeadlessPaneContext["apiClient"],
|
|
31
|
+
config: createDefaultConfig("/tmp/gloomberb-headless-sec"),
|
|
32
|
+
signal: new AbortController().signal,
|
|
33
|
+
};
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
function args(limit: number): HeadlessPaneLoadArgs {
|
|
37
|
+
return {
|
|
38
|
+
rawArgument: "AAPL",
|
|
39
|
+
argument: "AAPL",
|
|
40
|
+
symbols: ["AAPL"],
|
|
41
|
+
options: { limit },
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
describe("SEC headless model", () => {
|
|
46
|
+
test("projects filing rows and honors the page limit", async () => {
|
|
47
|
+
const requestedLimits: number[] = [];
|
|
48
|
+
const headless = createSecHeadless({
|
|
49
|
+
loadFilings: async (_symbol, limit) => {
|
|
50
|
+
requestedLimits.push(limit);
|
|
51
|
+
return filings;
|
|
52
|
+
},
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
const first = await headless.load(args(1), context());
|
|
56
|
+
expect(first.rows).toEqual([{
|
|
57
|
+
filedAt: "2026-08-25T00:00:00.000Z",
|
|
58
|
+
acceptedAt: null,
|
|
59
|
+
form: "8-K",
|
|
60
|
+
filing: "8-K | Results of Operations | Current Report",
|
|
61
|
+
items: "2.02,9.01",
|
|
62
|
+
accessionNumber: "0000320193-26-000100",
|
|
63
|
+
primaryDocument: null,
|
|
64
|
+
cik: "0000320193",
|
|
65
|
+
url: "https://www.sec.gov/filing/one",
|
|
66
|
+
}]);
|
|
67
|
+
|
|
68
|
+
const both = await headless.load(args(2), context());
|
|
69
|
+
expect(both.rows).toHaveLength(2);
|
|
70
|
+
expect(requestedLimits).toEqual([1, 2]);
|
|
71
|
+
});
|
|
72
|
+
});
|