Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion app/src/__tests__/angular-native-view.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -113,7 +113,15 @@ describe('Angular Native view', () => {

report({ web1: browser, an42: native });
await settle(fixture);
expect(tabNames(fixture)).toEqual(['Components', 'Signals', 'Injectors', 'Store', 'Pipes']);
expect(tabNames(fixture)).toEqual([
'Components',
'Signals',
'Injectors',
'Store',
'Queries',
'Pipes',
'Errors',
]);
expect(host(fixture).querySelector('nav button.active')?.textContent).toContain('Components');
expect(host(fixture).textContent).not.toContain('No Angular Native app is connected');
expect(hostPageId()).toBe('an42');
Expand Down
15 changes: 12 additions & 3 deletions app/src/__tests__/native-controls.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -100,18 +100,27 @@ describe('browser-only controls', () => {
});

it.each(['angular-native', 'nativescript'])(
'hides them for a %s page and keeps the tree',
'hides the picker and the live views for a %s page, and keeps recording and the tree',
async (platform) => {
const fixture = await render(platform);
const host = fixture.nativeElement as HTMLElement;
expect(buttons(fixture)).not.toContain('Pick component on page');
expect(buttons(fixture)).not.toContain('Record');
expect(host.querySelector('#cd-heading')).toBeNull();
expect(buttons(fixture)).toContain('Record');
expect(host.querySelector('#cd-heading')).not.toBeNull();
expect(buttons(fixture)).not.toContain('Flash checks');
expect(buttons(fixture)).not.toContain('Performance track');
expect(host.textContent).toContain('An app on a device has no DOM');
expect(host.textContent).toContain('App');
expect(host.querySelector('input[type="search"]')).not.toBeNull();
},
);

it('shows the live views for a browser page', async () => {
const fixture = await render();
expect(buttons(fixture)).toContain('Flash checks');
expect(buttons(fixture)).toContain('Performance track');
});

it('cancels a pick on its own page when the dropdown shows another page', async () => {
const { client, calls } = liveClient({ p1: page('p1'), p2: page('p2') });
const fixture = TestBed.createComponent(ComponentTree);
Expand Down
2 changes: 1 addition & 1 deletion app/src/app.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,7 +107,7 @@ const TAB_VIEW: Partial<Record<Tab, View>> = {
capacitor: 'capacitor',
};
const VIEW_TABS: Partial<Record<View, Tab[]>> = {
'angular-native': ['components', 'signals', 'injectors', 'store', 'pipes'],
'angular-native': ['components', 'signals', 'injectors', 'store', 'queries', 'pipes', 'errors'],
};

const NATIVESCRIPT_SETUP: ComingSoonInfo = {
Expand Down
7 changes: 6 additions & 1 deletion app/src/pages/cd-recording.ts
Original file line number Diff line number Diff line change
Expand Up @@ -71,11 +71,14 @@ const SHOWN_CYCLES = 30;
often each instance was checked. Times come from a development build. While it records, the
page also wraps Angular's scheduler, adds a signal write hook and watches DOM updates; all
of it stops with the recording.
@if (native()) {
An app on a device has no DOM, so checks that changed nothing are not counted there.
}
</p>
@if (page()?.supported === false) {
<app-angular-unavailable [page]="angularPage()" capability="profiler" missing compact />
}
@if (!imported()) {
@if (!imported() && !native()) {
<div class="views" role="group" aria-labelledby="cd-views-label">
<span id="cd-views-label" class="views-label">Live views</span>
<button
Expand Down Expand Up @@ -412,6 +415,8 @@ export class CdRecording {
readonly livePage = input<CdPage | null>(null, { alias: 'page' });
readonly pageId = input<string | null>(null);
readonly rpc = input<DevframeRpcClient | null>(null);
/** An app on a device: the live views draw on a DOM page, so they are left out. */
readonly native = input(false);
private readonly angularPages = injectAngularPages(() => this.rpc());
/** This page's Angular version, for the unavailable notes. */
readonly angularPage = computed(() => angularPageFor(this.angularPages(), this.pageId()));
Expand Down
3 changes: 2 additions & 1 deletion app/src/pages/component-tree.ts
Original file line number Diff line number Diff line change
Expand Up @@ -803,11 +803,12 @@ function storedDirectiveHosts(): boolean {
(showOwner)="showForOwner($event)"
(highlight)="highlight($event)"
/>
@if (!staticReport() && !native()) {
@if (!staticReport()) {
<app-cd-recording
[page]="cdPage()"
[pageId]="page()!.pageId"
[rpc]="rpc()"
[native]="native()"
(showComponent)="showCdComponent($event)"
/>
<app-leaks-section
Expand Down
6 changes: 5 additions & 1 deletion apps/docs/src/content/agents/tools.md
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ Change detection cycles recorded with Angular's profiler: the slowest components

Without `page`, it reads the page that is recording, and `record` goes to every connected page. A `page` that names no connected tab gets the list of connected tabs. A connected tab that has not recorded yet gets a hint to start a recording there. The answer starts with the change detection mode of the page it reports on: zoneless, zone.js, or zoneless with zone.js still loaded. Recording is off until the panel or this tool starts it. Call it with `record: "start"`, use the app, then call it again without `record`. With `agent.readOnly`, the tool has no `record` argument and is marked read-only, so start the recording from the panel. When older cycles were dropped at [`limits.cdCycles`](../getting-started/configuration.md#limits), the answer says how many. A recording lists at most 100 component classes and counts checks for up to 2000 instances. When it checked more, the answer says how many were left out.

`flash`, `track` and `angularProfiling` go to the page in `page`, or to every connected page, and can be combined with `record`, but not with `export`. They are the **Live views** toggles of the panel; see [Watch checks live](../inspectors/components.md#watch-checks-live). Once a page reports them, the answer ends with a line that says which views are on. With `agent.readOnly`, the tool has none of these three arguments.
`flash`, `track` and `angularProfiling` go to the page in `page`, or to every connected page, and can be combined with `record`, but not with `export`. An [Angular Native](../getting-started/angular-native.md) or [NativeScript](../getting-started/nativescript.md) app has no live views. The tool says there is no DOM when `page` names such an app, or when `page` is left out and every page that reports is one. `record` works on these apps as on a browser page. They are the **Live views** toggles of the panel; see [Watch checks live](../inspectors/components.md#watch-checks-live). Once a page reports them, the answer ends with a line that says which views are on. With `agent.readOnly`, the tool has none of these three arguments.

`export` only reads, so it stays available with `agent.readOnly`. The file is masked like other answers (JWTs, bearer tokens and secret names), has no instance check counts, and comes in a `json` code block to save as a `.json` file. An export over 100,000 characters is not returned, because cut JSON doesn't parse. The answer gives its size and the counts of cycles, timed spans, component classes, hook rows and output rows instead. Export it from the panel then, or clear the recording and record a shorter session. See [Export and import a recording](../inspectors/components.md#export-and-import-a-recording).

Expand Down Expand Up @@ -309,6 +309,8 @@ What the copy can't show:

The image needs an MCP client that shows image content. `agent.tools.components: false` hides the tool.

An [Angular Native](../getting-started/angular-native.md) or [NativeScript](../getting-started/nativescript.md) app has no DOM to copy. With its `page`, the tool says so without asking the app. Without `page`, the search skips those apps, and when only a device app reports, the answer says so.

### inspect-signals

The signal graph the page reported: nodes (`signal`, `computed`, `linkedSignal`, `effect`), dependency edges, the component or injector they belong to, and recent value history per node. Reads: page.
Expand Down Expand Up @@ -810,6 +812,8 @@ The audit reuses each inspector's own checks: `diagnose-change-detection` hints

The axe pass loads axe-core into the page on demand and changes nothing in the app. It leaves out the overlay's own elements and frames, masks selectors, attributes and summaries, and leaves out text content. With `agent.readOnly`, the tool has no `axe` argument and is marked read-only, and it refuses `session: "clear"` and a `session: "before"` that would replace a baseline, so it never drops one the user took. Two audits that used the same axe pass don't compare accessibility, and a note says so. A reload of the tab drops its axe pass. A session is kept per page for 30 minutes after its last use, so it survives a reload of the tab. Comparing only works within one page id. See [Inspect, fix and verify](../guides/inspect-fix-verify.md) for the loop.

On an [Angular Native](../getting-started/angular-native.md) or [NativeScript](../getting-started/nativescript.md) app, the audit reads its change detection, leaks and errors, and sessions work as on a browser page. The app has no DOM, so `axe: true` answers that the pass did not run and why, and **Coverage** lists accessibility as off.

## Analog

These tools cover *Analog apps. Most read your source. Some also read what the Vite plugin recorded.
Expand Down
Loading
Loading